windows下使用ZLMediaKit-API+FFmpeg+opengl拉取解码播放流媒体

ZLMediaKit简介

ZLMediaKit是一个基于C++11的高性能运营级流媒体服务框架,和SRS类似,功能强大,性能优异,提供多种支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/WebSocket-FLV/GB28181/HTTP-TS/WebSocket-TS/HTTP-fMP4/WebSocket-fMP4/MP4/WebRTC),支持协议互转功能相当强大。

需求

ZLMediaKit本身提供player播放流媒体,但是在windows下用着不方便,而且播放时总提示overload,我需求也比较简单,测试各种流媒体播放延时,使用VLC等第三方播放器测试延时难以确定。所以想使用ZLMediaKit提供的c接口API自己做解码显示,ffmpeg软解和opengl显示yuv420p/nv12/nv21有成熟轮子用都比较简单(感谢雷霄晔赐予的入行第一个轮子),所以就搭建工程,实现起来也比较简单,做下记录。

项目准备

  1. openssl库 ZlMediakit需要依赖openssl,直接去下载网站下载安装文件安装即可,我安装的是Win64 OpenSSL v1.1.1w
  2. ffmpeg库 解码必然要用到ffmpeg库,前往github编译地址下载编译好的压缩包,解压后包含头文件和动态库,我下载的是ffmpeg-master-latest-win64-gpl.zip
  3. freeflut库和glew库,使用opengl显示yuv需要使用这两个库,需要使用cmake创建vs工程,编译出需要的库文件,参考文章,具体过程也比较简单,下载解压源码,在源码下新建build文件夹,然后打开cmake-gui配置好源码目录和build工程文件存放目录,生成工程文件,使用vs编译即可。
  4. ZLMediaKit C api和mk_api.lib动态库,和之前一样,使用cmake-gui配置工程,编译后会在release\windows\Debug\Debug目录下生成mk_api.libmk_api.dll,头文件位于源码api\include目录下;
    注意先安装openssl,再编译ZLMediaKit

实现

ffmpeg软解

硬解可参考ffmpeg官方的hwdecode.c,。
VideoDecoder.h

#pragma once
#include <iostream>
#include <cstdint>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
}
typedef enum DecPixelFormat_ {DEC_FMT_NONE = -1,DEC_FMT_YUV420P,  ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per///< 2x2 Y samples)DEC_FMT_NV12,   ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane///< for the UV components, which are interleaved (first///< byte U and the following byte V)DEC_FMT_NV21,   ///< as above, but U and V bytes are swapped} DecPixelFormat;class VideoDecoder {
public:VideoDecoder(int32_t codec_id);// 0 : h264, 1 : hevc~VideoDecoder();uint8_t* decode(const uint8_t* src, uint32_t len, int32_t& pix_w, int32_t& pix_h, int32_t& format);private:int32_t AVPixelFormat2Format(int32_t av_fmt);private:AVCodecContext* dec_context = nullptr;
};

VideoDecoder.cpp

#include <iostream>
#include "videoDecoder.h"
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "swresample.lib")
VideoDecoder::VideoDecoder(int32_t codec_id)
{// 根据输入参数选择解码器const AVCodec* codec = nullptr;if (codec_id == 0) {codec = avcodec_find_decoder(AV_CODEC_ID_H264);}else if (codec_id == 1) {codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);}else {std::cerr << "unknow codec_id" << std::endl;}if (!codec) {std::cerr << "Codec [" << codec_id << "] not found\n";return;}dec_context = avcodec_alloc_context3(codec);if (!dec_context) {std::cerr << "Could not allocate video codec context\n";return;}// 打开解码器if (avcodec_open2(dec_context, codec, NULL) < 0) {std::cerr << "Could not open codec\n";return;}std::cout << "open codec success" << std::endl;
}VideoDecoder::~VideoDecoder()
{std::cout << "VideoDecoder::~VideoDecoder()" << std::endl;if (dec_context) {avcodec_free_context(&dec_context);dec_context = nullptr;}
}uint8_t* VideoDecoder::decode(const uint8_t* src, uint32_t len, int32_t& pix_w, int32_t& pix_h, int32_t& format)
{AVPacket* pkt = nullptr;AVFrame* frame = NULL;AVFrame* tmp_frame = NULL;uint8_t* buffer = NULL;AVPixelFormat tmp_pixFmt = AV_PIX_FMT_NONE;int size = 0;pix_w = 0;pix_h = 0;format = DEC_FMT_NONE;if (dec_context == nullptr) {std::cerr << "dec_context is nullptr\n";return NULL;}if (src == nullptr || len == 0) {std::cerr << "src or len is null" << std::endl;return nullptr;}pkt = av_packet_alloc();if (pkt == nullptr) {std::cerr << "Could not allocate packet\n";return NULL;}pkt->data = const_cast<uint8_t*>(src);  // FFmpeg expects non-const data pointerpkt->size = len;int ret = avcodec_send_packet(dec_context, pkt);if (ret < 0) {av_packet_free(&pkt);pkt = nullptr;fprintf(stderr, "Error during decoding\n");return buffer;}while (1) {if (!(frame = av_frame_alloc())) {fprintf(stderr, "Can not alloc frame\n");ret = AVERROR(ENOMEM);goto fail;}ret = avcodec_receive_frame(dec_context, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {av_frame_free(&frame);return 0;}else if (ret < 0) {fprintf(stderr, "Error while decoding\n");goto fail;}tmp_frame = frame;tmp_pixFmt = static_cast<AVPixelFormat>(tmp_frame->format);size = av_image_get_buffer_size(tmp_pixFmt, tmp_frame->width, tmp_frame->height, 1);buffer = (uint8_t*)malloc(sizeof(uint8_t) * size);if (!buffer) {fprintf(stderr, "Can not alloc buffer\n");ret = AVERROR(ENOMEM);goto fail;}ret = av_image_copy_to_buffer(buffer, size,(const uint8_t* const*)tmp_frame->data,(const int*)tmp_frame->linesize, tmp_pixFmt,tmp_frame->width, tmp_frame->height, 1);if (ret < 0) {fprintf(stderr, "Can not copy image to buffer\n");goto fail;}pix_w = tmp_frame->width;pix_h = tmp_frame->height;format = AVPixelFormat2Format(tmp_pixFmt);fail:av_packet_free(&pkt);pkt = nullptr;av_frame_free(&frame);frame = NULL;if (ret < 0 && buffer != NULL) {av_free(buffer);buffer = NULL;}return buffer;}return buffer;
}int32_t VideoDecoder::AVPixelFormat2Format(int32_t pix_fmt)
{switch (pix_fmt) {case AV_PIX_FMT_YUV420P:case AV_PIX_FMT_YUVJ420P:return DEC_FMT_YUV420P;case AV_PIX_FMT_NV12:return DEC_FMT_NV12;case AV_PIX_FMT_NV21:return DEC_FMT_NV21;default:return DEC_FMT_NONE;}return DEC_FMT_NONE;
}

opengl显示YUV

yuvDisplayer.h

#pragma once#include <iostream>
#define FREEGLUT_STATIC
#include "GL/glew.h"
#include "GL/glut.h"#define ATTRIB_VERTEX 3
#define ATTRIB_TEXTURE 4
// Rotate the texture
#define TEXTURE_ROTATE 0
// Show half of the Texture
#define TEXTURE_HALF 0
typedef enum DisPixelFormat_ {DIS_FMT_NONE = -1,DIS_FMT_YUV420P,  ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per///< 2x2 Y samples)DIS_FMT_NV12,   ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane///< for the UV components, which are interleaved (first///< byte U and the following byte V)DIS_FMT_NV21,   ///< as above, but U and V bytes are swapped
} DisPixelFormat;class YUVDisplayer {
public:YUVDisplayer(const std::string& title = "YUV Renderer", const int32_t x = -1, const int32_t y = -1);~YUVDisplayer();void display(const uint8_t* pData, int width, int height, int32_t format = DIS_FMT_NV12);static bool getWindowSize(int32_t& width, int32_t& height);private:bool bInit;int32_t gl_format;std::string gl_title;int32_t gl_x;int32_t gl_y;GLuint program;GLuint id_y, id_u, id_v, id_uv;  // Texture idGLuint textureY, textureU, textureV, textureUV;void initGL(int width, int height, int32_t format);bool InitShaders(int32_t format);void initTextures(int32_t format);void initYV12Textures();void initNV12Textures();void display_yv12(const uint8_t* pData, int width, int height);void display_nv12(const uint8_t* pData, int width, int height);
};

yuvDisplayer.cpp

#include <string.h>#include <chrono>
#include <iostream>
#include <vector>
#include "yuvDisplayer.h"
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glew32s.lib")// Helper function to compile shaders
static const char* vs = "attribute vec4 vertexIn;\n"
"attribute vec2 textureIn;\n"
"varying vec2 textureOut;\n"
"void main(void){\n"
"    gl_Position = vertexIn;\n"
"    textureOut = textureIn;\n"
"}\n";static const char* fs_yuv420p = "varying vec2 textureOut;\n"
"uniform sampler2D tex_y;\n"
"uniform sampler2D tex_u;\n"
"uniform sampler2D tex_v;\n"
"void main(void){\n"
"    vec3 yuv;\n"
"    vec3 rgb;\n"
"    yuv.x = texture2D(tex_y, textureOut).r;\n"
"    yuv.y = texture2D(tex_u, textureOut).r - 0.5;\n"
"    yuv.z = texture2D(tex_v, textureOut).r - 0.5;\n"
"    rgb = mat3( 1,       1,         1,\n"
"                0,       -0.39465,  2.03211,\n"
"                1.13983, -0.58060,  0) * yuv;\n"
"    gl_FragColor = vec4(rgb, 1);\n"
"}\n";static const char* fs_nv12 = "varying vec2 textureOut;\n"
"uniform sampler2D tex_y;\n"
"uniform sampler2D tex_uv;\n"
"void main(void) {\n"
"    vec3 yuv;\n"
"    vec3 rgb;\n"
"    yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n"
"    vec2 uv = texture2D(tex_uv, textureOut).rg - vec2(0.5, 0.5);\n"
"    yuv.y = uv.x;\n"
"    yuv.z = uv.y;\n"
"    rgb = mat3(1.164,     1.164,     1.164,\n"
"               0.0,      -0.391,     2.018,\n"
"               1.596,    -0.813,     0.0) * yuv;\n"
"    gl_FragColor = vec4(rgb, 1.0);\n"
"}\n";static const char* fs_nv21 = "varying vec2 textureOut;\n"
"uniform sampler2D tex_y;\n"
"uniform sampler2D tex_uv;\n"
"void main(void) {\n"
"    vec3 yuv;\n"
"    vec3 rgb;\n"
"    yuv.x = texture2D(tex_y, textureOut).r;\n"
"    vec2 uv = texture2D(tex_uv, textureOut).rg - vec2(0.5, 0.5);\n"
"    yuv.y = uv.y;\n"
"    yuv.z = uv.x;\n"
"    rgb = mat3(1,       1,         1,\n"
"               0,       -0.39465,  2.03211,\n"
"               1.13983, -0.58060,  0) * yuv;\n"
"    gl_FragColor = vec4(rgb, 1);\n"
"}\n";#if TEXTURE_ROTATE
static const GLfloat vertexVertices[] = {-1.0f, -0.5f,0.5f, -1.0f,-0.5f,  1.0f,1.0f,  0.5f,
};
#else
static const GLfloat vertexVertices[] = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f,  1.0f,1.0f,  1.0f,
};
#endif#if TEXTURE_HALF
static const GLfloat textureVertices[] = {0.0f,  1.0f,0.5f,  1.0f,0.0f,  0.0f,0.5f,  0.0f,
};
#else
static const GLfloat textureVertices[] = {0.0f,  1.0f,1.0f,  1.0f,0.0f,  0.0f,1.0f,  0.0f,
};
#endifYUVDisplayer::YUVDisplayer(const std::string& title, const int32_t x, const int32_t y) : bInit(false), gl_x(x), gl_y(y), gl_format(-1), textureY(0), textureU(0), textureV(0), textureUV(0),id_y(0), id_u(0), id_v(0), id_uv(0), program(0)
{if (title.empty())gl_title = "opengl yuv display";elsegl_title = title;
}YUVDisplayer::~YUVDisplayer() {if (!bInit) return;glDeleteTextures(1, &textureY);if (textureY) glDeleteTextures(1, &textureY);if (textureU) glDeleteTextures(1, &textureU);if (textureV) glDeleteTextures(1, &textureV);if (textureUV) glDeleteTextures(1, &textureUV);glDeleteProgram(program);bInit = false;
}void YUVDisplayer::initGL(int32_t width, int32_t height, int32_t format)
{int xpos = 0, ypos = 0;int32_t screen_w = 0;int32_t screen_h = 0;int32_t width_win = width;int32_t height_win = height;if (!getWindowSize(screen_w, screen_h)) {screen_w = 1920;screen_h = 1080;std::cerr << "getWindowSize failed!" << std::endl;}if (width > (screen_w * 2 / 3) || height > (screen_h * 2 / 3)) {width_win = (width * 2 / 3);height_win = (height * 2 / 3);}else {width_win = width;height_win = height;}if (gl_x < 0 || gl_y < 0) {xpos = (screen_w - width_win) / 2;ypos = (screen_h - height_win) / 2;}else {xpos = gl_x;ypos = gl_y;}int argc = 1;char argv[1][16] = { 0 };strcpy_s(argv[0], sizeof(argv[0]), "YUV Renderer");char* pargv[1] = { argv[0] };glutInit(&argc, pargv);// GLUT_DOUBLEglutInitDisplayMode(GLUT_DOUBLE |GLUT_RGBA /*| GLUT_STENCIL | GLUT_DEPTH*/);glutInitWindowPosition(xpos, ypos);glutInitWindowSize(width_win, height_win);glutCreateWindow(gl_title.c_str());GLenum glewState = glewInit();if (GLEW_OK != glewState) {std::cerr << "glewInit failed!" << std::endl;return;}InitShaders(format);initTextures(format);gl_format = format;const uint8_t* glVersion = glGetString(GL_VERSION);bInit = true;std::cout << "opengl(" << glVersion << ") init success!" << std::endl;
}bool YUVDisplayer::getWindowSize(int32_t& width, int32_t& height) {
#if 0Display* display = XOpenDisplay(NULL);if (!display) {std::cerr << "Unable to open X display" << std::endl;return false;}int screen_num = DefaultScreen(display);width = DisplayWidth(display, screen_num);height = DisplayHeight(display, screen_num);XCloseDisplay(display);
#endifwidth = 1920;height = 1080;return true;
}// Init Shader
bool YUVDisplayer::InitShaders(int32_t format) {GLint vertCompiled, fragCompiled, linked;GLint v, f;// Shader: step1v = glCreateShader(GL_VERTEX_SHADER);f = glCreateShader(GL_FRAGMENT_SHADER);// Get source code// Shader: step2glShaderSource(v, 1, &vs, NULL);if (format == DIS_FMT_YUV420P)glShaderSource(f, 1, &fs_yuv420p, NULL);else if (format == DIS_FMT_NV12)glShaderSource(f, 1, &fs_nv12, NULL);else if (format == DIS_FMT_NV21)glShaderSource(f, 1, &fs_nv12, NULL);else {std::cerr << "InitShaders Invalid format [" << format << "]" << std::endl;return false;}// Shader: step3glCompileShader(v);// DebugglGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled);glCompileShader(f);glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);// Program: Step1program = glCreateProgram();// Program: Step2glAttachShader(program, v);glAttachShader(program, f);glBindAttribLocation(program, ATTRIB_VERTEX, "vertexIn");glBindAttribLocation(program, ATTRIB_TEXTURE, "textureIn");// Program: Step3glLinkProgram(program);// DebugglGetProgramiv(program, GL_LINK_STATUS, &linked);// Program: Step4glUseProgram(program);return true;
}void YUVDisplayer::initYV12Textures()
{textureY = glGetUniformLocation(program, "tex_y");textureU = glGetUniformLocation(program, "tex_u");textureV = glGetUniformLocation(program, "tex_v");// Set ArraysglVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);// Enable itglEnableVertexAttribArray(ATTRIB_VERTEX);glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);glEnableVertexAttribArray(ATTRIB_TEXTURE);// Init TextureglGenTextures(1, &id_y);glBindTexture(GL_TEXTURE_2D, id_y);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glGenTextures(1, &id_u);glBindTexture(GL_TEXTURE_2D, id_u);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glGenTextures(1, &id_v);glBindTexture(GL_TEXTURE_2D, id_v);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}void YUVDisplayer::initNV12Textures()
{textureY = glGetUniformLocation(program, "tex_y");textureUV = glGetUniformLocation(program, "tex_uv");// Set ArraysglVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);// Enable itglEnableVertexAttribArray(ATTRIB_VERTEX);glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);glEnableVertexAttribArray(ATTRIB_TEXTURE);// Init TextureglGenTextures(1, &id_y);glBindTexture(GL_TEXTURE_2D, id_y);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glGenTextures(1, &id_uv); // 注意这里只需要一个纹理用于UV分量glBindTexture(GL_TEXTURE_2D, id_uv);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}void YUVDisplayer::initTextures(int32_t format)
{// Get Uniform Variables Locationif (format == DIS_FMT_YUV420P)initYV12Textures();else if (format == DIS_FMT_NV12 || format == DIS_FMT_NV21)initNV12Textures();else {std::cerr << "initTextures error Invalid format [" << format << "]" << std::endl;return;}
}void YUVDisplayer::display(const uint8_t* yuv, int32_t pixel_w, int32_t pixel_h, int32_t format)
{if (yuv == nullptr) {std::cerr << "yuv nullptr error!" << std::endl;return;}if (pixel_w <= 0 || pixel_h <= 0) {std::cerr << "Invalid width or height!" << std::endl;return;}if (format != DIS_FMT_YUV420P && format != DIS_FMT_NV12 && format != DIS_FMT_NV21) {std::cerr << "displayYUVData Invalid format error " << format << std::endl;return;}if (!bInit) {initGL(pixel_w, pixel_h, format);return;}if (pixel_w <= 0 || pixel_h <= 0) {std::cerr << "Invalid width or height!" << std::endl;return;}if (yuv == nullptr) {std::cerr << "yuv nullptr error!" << std::endl;return;}if (gl_format == DIS_FMT_YUV420P)display_yv12(yuv, pixel_w, pixel_h);else if (gl_format == DIS_FMT_NV12 || gl_format == DIS_FMT_NV21)display_nv12(yuv, pixel_w, pixel_h);else {std::cerr << "displayYUVData Invalid format error " << gl_format << std::endl;return;}
}void YUVDisplayer::display_yv12(const uint8_t* yuv, int pixel_w, int pixel_h)
{const unsigned char* y = yuv;const unsigned char* u = y + pixel_w * pixel_h;const unsigned char* v = u + pixel_w * pixel_h / 4;//ClearglClearColor(0.0, 255, 0.0, 0.0);glClear(GL_COLOR_BUFFER_BIT);//Y//glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, id_y);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w, pixel_h, 0, GL_RED, GL_UNSIGNED_BYTE, y);glUniform1i(textureY, 0);//UglActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, id_u);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w / 2, pixel_h / 2, 0, GL_RED, GL_UNSIGNED_BYTE, u);glUniform1i(textureU, 1);//VglActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, id_v);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w / 2, pixel_h / 2, 0, GL_RED, GL_UNSIGNED_BYTE, v);glUniform1i(textureV, 2);// DrawglDrawArrays(GL_TRIANGLE_STRIP, 0, 4);// Show//DoubleglutSwapBuffers();//Single//glFlush();
}void YUVDisplayer::display_nv12(const uint8_t* yuv, int pixel_w, int pixel_h)
{const unsigned char* y = yuv;const unsigned char* uv = y + pixel_w * pixel_h;//ClearglClearColor(0.0, 0.0, 0.0, 1.0);glClear(GL_COLOR_BUFFER_BIT);//YglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, id_y);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w, pixel_h, 0, GL_RED, GL_UNSIGNED_BYTE, y);glUniform1i(textureY, 0);//UVglActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, id_uv);glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, pixel_w / 2, pixel_h / 2, 0, GL_RG, GL_UNSIGNED_BYTE, uv);glUniform1i(textureUV, 1); // 注意这里我们使用UV纹理和uniform,不再需要单独的U和V// DrawglDrawArrays(GL_TRIANGLE_STRIP, 0, 4);// ShowglutSwapBuffers();
}

组帧

对ZLMediaKit获取的帧格式转为便于解码的格式
pullFramer.h

#pragma once
#include <iostream>
#include <memory>
#include <functional>
#include "mk_frame.h"
class FrameData {
public:using Ptr = std::shared_ptr<FrameData>;static Ptr CreateShared(const mk_frame frame) {return Ptr(new FrameData(frame));}static Ptr CreateShared(uint8_t* data_, size_t size_, const mk_frame frame) {return Ptr(new FrameData(data_, size_, frame));}~FrameData();uint64_t dts() { return dts_; };uint64_t pts() const { return pts_; }size_t prefixSize() const { return prefixSize_; }bool keyFrame() const { return keyFrame_; }bool dropAble() const { return dropAble_; }bool configFrame() const { return configFrame_; }bool decodeAble() const { return decodeAble_; }uint8_t* data() const { return data_; }size_t size() const { return size_; }friend std::ostream& operator<<(std::ostream& os, const FrameData::Ptr& Frame);
private:FrameData(const mk_frame frame);FrameData(uint8_t *data_, size_t size_, const mk_frame frame);FrameData(const FrameData& other) = delete;
private:uint64_t dts_;uint64_t pts_;size_t prefixSize_;bool keyFrame_;bool dropAble_;bool configFrame_;bool decodeAble_;uint8_t* data_;size_t size_;
};class PullFramer {
public:using Ptr = std::shared_ptr<PullFramer>;using onGetFrame = std::function<void(const FrameData::Ptr&, void* decoder, void* displayer)>;// using onConver = std::function<void(const FFmpegFrame::Ptr &)>;static PullFramer::Ptr CreateShared() {return std::make_shared<PullFramer>();}void setOnGetFrame(const onGetFrame& onGetFrame, void* decoder = nullptr, void* displayer = nullptr);PullFramer();~PullFramer();bool onFrame(const mk_frame frame);private:onGetFrame cb_;void* decoder_;void* displayer_;unsigned char* configFrames;size_t configFramesSize;void clearConfigFrames();
};

pullFramer.cpp

#include <iostream>
#include <iomanip>
#include "PullFramer.h"static std::string dump(const void* buf, size_t size)
{int len = 0;char* data = (char*)buf;std::string result = "NULL";if (data == NULL || size <= 0)return result;size_t total = size * 3 + 1;char* buff = new char[size * 3 + 1];memset(buff, 0, size * 3 + 1);for (size_t i = 0; i < size; i++) {len += sprintf_s(buff + len, total-len, "%.2x ", (data[i] & 0xff));}result = std::string(buff);delete[] buff;buff = NULL;return result;
}FrameData::FrameData(const mk_frame frame): data_(nullptr), size_(0), dts_(0), pts_(0), prefixSize_(0),keyFrame_(false), configFrame_(false), dropAble_(true), decodeAble_(false)
{if (frame == NULL) {std::cerr << "frame is NULL" << std::endl;return;}auto data = mk_frame_get_data(frame);auto size = mk_frame_get_data_size(frame);if (data == NULL || size == 0) {std::cerr << "data is NULL or size is 0" << std::endl;return;}data_ = new uint8_t[size + 1];if (data_ == nullptr) {std::cout << "new failed" << std::endl;return;}size_ = size;memcpy(data_, data, size_);data_[size_] = 0;dts_ = mk_frame_get_dts(frame);pts_ = mk_frame_get_pts(frame);prefixSize_ = mk_frame_get_data_prefix_size(frame);auto flag = mk_frame_get_flags(frame);keyFrame_ = (flag & MK_FRAME_FLAG_IS_KEY);configFrame_ = (flag & MK_FRAME_FLAG_IS_CONFIG);dropAble_ = (flag & MK_FRAME_FLAG_DROP_ABLE);decodeAble_ = (flag & MK_FRAME_FLAG_NOT_DECODE_ABLE);
}FrameData::FrameData(uint8_t *data, size_t size, const mk_frame frame): data_(nullptr), size_(0), dts_(0), pts_(0), prefixSize_(0),keyFrame_(false), configFrame_(false), dropAble_(true), decodeAble_(false)
{if (frame == NULL) {std::cerr << "frame is NULL" << std::endl;return;}if (data == NULL || size == 0) {std::cerr << "data is NULL or size is 0" << std::endl;return;}data_ = new uint8_t[size + 1];if (data_ == nullptr) {std::cout << "new failed" << std::endl;return;}size_ = size;memcpy(data_, data, size_);data_[size_] = 0;dts_ = mk_frame_get_dts(frame);pts_ = mk_frame_get_pts(frame);prefixSize_ = mk_frame_get_data_prefix_size(frame);auto flag = mk_frame_get_flags(frame);keyFrame_ = (flag & MK_FRAME_FLAG_IS_KEY);configFrame_ = (flag & MK_FRAME_FLAG_IS_CONFIG);dropAble_ = (flag & MK_FRAME_FLAG_DROP_ABLE);decodeAble_ = (flag & MK_FRAME_FLAG_NOT_DECODE_ABLE);
}FrameData::~FrameData()
{if (data_ != nullptr) {delete[] data_;data_ = nullptr;}size_ = 0;
}std::ostream& operator<<(std::ostream& os, const FrameData::Ptr& Frame)
{size_t len = 10;if (Frame.get() == nullptr || Frame->data() == nullptr || Frame->size() == 0) {os << "NULL";return os;}if (Frame->size() < 10) {len = Frame->size();}os << "[" << Frame->pts() << ", drop:" << Frame->dropAble() << ", key:" << Frame->keyFrame() << ", "<< std::setw(6) << std::right << Frame->size() << "] : "<< dump(Frame->data(), len);return os;
}PullFramer::PullFramer()
{configFrames = NULL;configFramesSize = 0;cb_ = NULL;decoder_ = NULL;displayer_ = NULL;
}PullFramer::~PullFramer()
{clearConfigFrames();
}void PullFramer::setOnGetFrame(const onGetFrame& onGetFrame, void* decoder, void* displayer)
{cb_ = onGetFrame;decoder_ = decoder;displayer_ = displayer;
}void PullFramer::clearConfigFrames()
{if (configFrames != NULL) {free(configFrames);configFrames = nullptr;}configFramesSize = 0;
}bool PullFramer::onFrame(const mk_frame frame_)
{if (frame_ == NULL) {return false;}auto frame = FrameData::CreateShared(frame_);if (frame.get() == nullptr || frame->data() == nullptr || frame->size() == 0) {return false;}auto data = frame->data();auto size = frame->size();if (frame->configFrame()) {size_t newSize = configFramesSize + size;unsigned char* newConfigFrames = (unsigned char*)realloc(configFrames, newSize);if (newConfigFrames == NULL) {std::cout << "realloc failed" << std::endl;clearConfigFrames();return false;}configFrames = newConfigFrames;memcpy(configFrames + configFramesSize, data, size);configFramesSize = newSize;}else {if (configFrames != NULL && configFramesSize != 0) {if (frame->dropAble()) {size_t newSize = configFramesSize + size;unsigned char* newConfigFrames = (unsigned char*)realloc(configFrames, newSize);if (newConfigFrames == NULL) {std::cout << "realloc failed" << std::endl;clearConfigFrames();return false;}configFrames = newConfigFrames;memcpy(configFrames + configFramesSize, data, size);configFramesSize = newSize;return true;}size_t totalSize = configFramesSize + size;unsigned char* mergedData = (unsigned char*)malloc(totalSize);if (mergedData == NULL) {std::cout << "malloc failed" << std::endl;clearConfigFrames();return false;}memcpy(mergedData, configFrames, configFramesSize);memcpy(mergedData + configFramesSize, data, size);clearConfigFrames();if (cb_) {cb_(FrameData::CreateShared(mergedData, totalSize, frame_), decoder_, displayer_);}else {printf("on Frame %zu [%.2x %.2x %.2x %.2x %.2x %.2x]\n", totalSize, mergedData[0], mergedData[1], mergedData[2], mergedData[3], (mergedData[4] & 0xff), (mergedData[5] & 0xff));}free(mergedData);mergedData = NULL;return true;}else {if (cb_) {cb_(FrameData::CreateShared(frame_), decoder_, displayer_);}else {printf("on Frame %zu [%.2x %.2x %.2x %.2x %.2x %.2x]\n", size, data[0], data[1], data[2], data[3], (data[4] & 0xff), (data[5] & 0xff));}return true;}}return true;
}

主函数

实现拉流解码显示流程

// zlmplayer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <chrono>
#include "yuvDisplayer.h"
#include "videoDecoder.h"
#include "pullFramer.h"
#include "mk_mediakit.h"
#pragma comment(lib, "mk_api.lib")typedef struct {void* puller;void* decoder;
} Context;void API_CALL on_track_frame_out(void* user_data, mk_frame frame) {Context* ctx = (Context*)user_data;auto pullerPtr = static_cast<std::shared_ptr<PullFramer>*>(ctx->puller);std::shared_ptr<PullFramer> puller = *pullerPtr;if (puller) {puller->onFrame(frame);}
}void API_CALL on_mk_play_event_func(void* user_data, int err_code, const char* err_msg, mk_track tracks[],int track_count) {// Context* ctx = (Context*)user_data;if (err_code == 0) {//successlog_debug("play success!");int i;for (i = 0; i < track_count; ++i) {if (mk_track_is_video(tracks[i])) {Context* ctx = (Context*)user_data;auto decoderPtr = static_cast<std::shared_ptr<VideoDecoder>*>(ctx->decoder);std::shared_ptr<VideoDecoder> decoder = *decoderPtr;if (decoder == nullptr) {decoder = std::make_shared<VideoDecoder>(mk_track_codec_id(tracks[i]));*decoderPtr = decoder;}log_info("got video track: %s", mk_track_codec_name(tracks[i]));//ctx->video_decoder = mk_decoder_create(tracks[i], 0);//mk_decoder_set_cb(ctx->video_decoder, on_frame_decode, user_data);//监听track数据回调mk_track_add_delegate(tracks[i], on_track_frame_out, user_data);}}}else {log_warn("play failed: %d %s", err_code, err_msg);}
}void API_CALL on_mk_shutdown_func(void* user_data, int err_code, const char* err_msg, mk_track tracks[], int track_count) {log_warn("play interrupted: %d %s", err_code, err_msg);
}void onGetFrame(const FrameData::Ptr& frame, void* userData1, void* userData2)
{// std::cout <<frame<<std::endl;auto decoderPtr = static_cast<std::shared_ptr<VideoDecoder>*>(userData1);std::shared_ptr<VideoDecoder> decoder = *decoderPtr;auto displayerPtr = static_cast<std::shared_ptr<YUVDisplayer>*>(userData2);std::shared_ptr<YUVDisplayer> displayer = *displayerPtr;if (decoder) {int32_t pixel_width = 0;int32_t pixel_height = 0;int32_t pixel_format = 0;auto start = std::chrono::steady_clock::now();auto dstYUV = decoder->decode(frame->data(), frame->size(), pixel_width, pixel_height, pixel_format);if (dstYUV == nullptr) {std::cerr << "decode error" << std::endl;return;}auto end = std::chrono::steady_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();// std::cout<<"width:"<<pixel_width<<" height:"<<pixel_height<<" format:"<<pixel_format<<std::endl;std::cout << "decode cost " << duration << " ms" << std::endl;
#if 1if (displayer) {start = end;displayer->display(dstYUV, pixel_width, pixel_height, pixel_format);end = std::chrono::steady_clock::now();duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();std::cout << "display cost " << duration << " ms" << std::endl;}
#endiffree(dstYUV);dstYUV = nullptr;}
}int main(int argc, char *argv[])
{mk_config config;config.ini = NULL;config.ini_is_path = 0;config.log_level = 0;config.log_mask = LOG_CONSOLE;config.ssl = NULL;config.ssl_is_path = 1;config.ssl_pwd = NULL;config.thread_num = 0;std::string url = "";printf("%d %s\n", argc, argv[0]);if (argc == 1) {url = "rtsp://admin:zmj123456@192.168.119.39";}else {url = argv[1];}mk_env_init(&config);auto player = mk_player_create();auto puller = PullFramer::CreateShared();auto displayer = std::make_shared<YUVDisplayer>(url);std::shared_ptr<VideoDecoder> decoder = nullptr;puller->setOnGetFrame(onGetFrame, static_cast<void*>(&decoder), static_cast<void*>(&displayer));Context ctx;memset(&ctx, 0, sizeof(Context));ctx.puller = static_cast<void*>(&puller);ctx.decoder = static_cast<void*>(&decoder);mk_player_set_on_result(player, on_mk_play_event_func, &ctx);mk_player_set_on_shutdown(player, on_mk_shutdown_func, NULL);mk_player_set_option(player, "rtp_type", "0");mk_player_set_option(player, "protocol_timeout_ms", "5000");mk_player_play(player, url.c_str());std::cout << "Hello World!\n";log_info("enter any key to exit");getchar();if (player) {mk_player_release(player);}return 0;
}

vs工程其他配置

使用多字节字符集,避免处理字符繁琐过程。
在这里插入图片描述

需要配置几个第三方库头文件和lib库位置,如下图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行目录下需要ffmpeg,glew,mk-api第三方库(freeglut我使用静态库编译)
在这里插入图片描述

运行效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/801612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Springboot考研资讯平台的设计与实现(论文+源码)_kaic

摘 要 随着现在网络的快速发展&#xff0c;网络的应用在各行各业当中它很快融入到了许多学校的眼球之中&#xff0c;他们利用网络来做这个电商的服务&#xff0c;随之就产生了“考研资讯平台”&#xff0c;这样就让学生考研资讯平台更加方便简单。 对于本考研资讯平台的设计来…

Python爬虫技术快速入门

一、简介 Python爬虫是一种自动化提取互联网上数据的技术&#xff0c;它可以访问网页、解析HTML内容&#xff0c;并从中提取有用的信息。本文将介绍如何使用Python编写简单而有效的爬虫程序。 二、准备工作 在编写Python爬虫之前&#xff0c;我们需要安装一些必要的库。以下…

计算机网络 网络命令的使用

一、实验内容 1.PING网络命令的实验 ping 127.0.0.1(内部回环测试)ping 本主机的IP地址ping 默认网关地址ping远端目的地的IP地址ping localhostping域名 2.其他网络命令实验 命令用途ipconfig/all 显示当前系统网络配置&#xff0c;包括IP地址、子网掩码、默认网关等trace…

Ubuntu 22.04进行远程桌面连接

文心一言 Ubuntu 22.04进行远程桌面连接&#xff0c;无论是连接到Windows 10还是另一个Ubuntu 22.04&#xff0c;都可以通过不同的方式实现。以下是具体的步骤&#xff1a; 连接到Windows 10 在Windows 10上开启远程桌面功能&#xff1a;首先&#xff0c;需要在Windows 10上…

Java设计模式之创建型模式(二)原型模式

原型模式 1、原型模式1-1、应用场景1-2、举个 软栗子1-3、举个 硬栗子1-3、举个实务栗子 学习原型模式的目的&#xff1a;原型模式的目的在于通过复制现有的实例来创建新的对象&#xff0c;以避免通过构造函数创建对象时可能带来的性能开销&#xff0c;同时可以控制对象的深度复…

pikachu 靶场通关(全)

一.暴力破解 1.1基于表单的暴力破解 1.1.1 漏洞利用 burp抓包&#xff0c;ctrli 添加爆破 根据返回长度得到两个用户 admin 123456 test abc123 1.1.2 源代码分析 文件路径在&#xff1a;vul/burteforce/bf_form.php 通过注释以及说明了&#xff0c;没有验证码也没有其他的…

【C语言】结构体、枚举、联合(自定义类型)

文章目录 前言一、结构体1.结构体的声明2.结构体的自引用3.结构体变量的定义和初始化4.结构体成员的访问5.结构体内存对齐&#xff08;重点&#xff09;6.#pragma修改默认对齐数7.结构体传参 二、位段1.位段的声明2.位段的内存分配3.位段的跨平台问题 三、枚举四、联合 &#x…

全速前进:2024年MAD(机器学习,人工智能和数据)前景(20000字长文)

THE 2024 MAD (MACHINE LEARNING, ARTIFICIAL INTELLIGENCE & DATA) LANDSCAPE 是FirstMark对数据、分析、机器学习和人工智能生态系统的第十次年度展望和「现状」描述 。 在这个领域的10多年里&#xff0c;事情从来没有像今天这样令人兴奋和充满希望。我们多年来描述的所…

编译Nginx配置QUIC/HTTP3.0

1. 安装BoringSSL sudo apt update sudo apt install -y build-essential ca-certificates zlib1g-dev libpcre3 \ libpcre3-dev tar unzip libssl-dev wget curl git cmake ninja-build mercurial \ libunwind-dev pkg-configgit clone --depth1 https://github.com/google/b…

Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus

Web 后台项目&#xff0c;权限如何定义、设置、使用&#xff1a;菜单权限、按钮权限 ts element-ui-Plus 做一个后台管理项目&#xff0c;里面需要用到权限管理。这里说一下权限定义的大概&#xff0c;代码不多&#xff0c;主要讲原理和如何实现它。 一、权限管理的原理 权限…

自动驾驶基础技术-无迹卡尔曼滤波UKF

自动驾驶基础技术-无迹卡尔曼滤波UKF Unscented Kalman Filter是解决非线性卡尔曼滤波的另一种思路&#xff0c;它利用Unscented Transform来解决概率分布非线性变换的问题。UnScented Kalman Filter不需要像Extended Kalman Filter一样计算Jacobin矩阵&#xff0c;在计算量大…

Pixel 手机上连接提示受阻,无法上网-解决方法

命令行中输入 adb shell settings delete global captive_portal_https_urladb shell settings delete global captive_portal_http_url输入服务器信息 adb shell settings put global captive_portal_http_url http://connect.rom.miui.com/generate_204adb shell settings …

从零开始:构建、打包并上传个人前端组件库至私有npm仓库的完整指南

文章目录 一、写组件1、注册全局组件方法2、组件13、组件2 二、测试三、发布1、配置package.json2、生成库包3、配置发布信息4、发布 四、使用1、安装2、使用 五、维护1、维护和更新2、注意事项 一、写组件 确定组件库的需求和功能&#xff1a;在开始构建组件库之前&#xff0c…

基于jenkins+gitlab+docker部署zabbix

背景 我现在已经在一台服务器上部署了jenkins和gitlab&#xff0c;现在有一个场景是需要在服务器上再部署一个zabbix&#xff0c;需要通过jenkins加上gitlab部署&#xff0c;并且要求zabbix是通过docker部署的 前提条件 jenkins、gitlab已完成部署并能正常访问&#xff0c;服…

再探Java为面试赋能(二)Java基础知识(二)反射机制、Lambda表达式、多态

文章目录 前言1.4 反射机制1.4.1 Class对象的获取1.4.2 Class类的方法1.4.3 通过反射机制修改只读类的属性 1.5 Lambda表达式1.5.1 函数式接口1.5.2 Lambda表达式的使用 1.6 多态1.6.1 多态的概念1.6.2 多态的实现条件1.6.3 重载&#xff08;Overload&#xff09;和重写&#x…

大数据之搭建Hive组件

声明&#xff1a;所有软件自行下载&#xff0c;并存放到统一目录中 1.Hive组件的安装配置 1.1实验环境 服务器集群3 个以上节点&#xff0c;节点间网络互通&#xff0c;各节点最低配置&#xff1a;双核 CPU、8GB 内存、100G 硬盘运行环境CentOS 7.4服务和组件完成前面章节实验…

Java基础笔记(一)

一、面向对象高级基础 1.Java的动态绑定机制 public class DynamicBinding {public static void main(String[] args) {//a 的编译类型 A, 运行类型 BA a new B();//向上转型System.out.println(a.sum());//40 子类sum()注释后-> 30System.out.println(a.sum1());//30 子类…

android-自定义TextView在文字内容末尾添加图片icon、可以添加间距

样式示意图 自定义属性 style.xml <declare-styleable name"IconLabelTextView"><attr name"iconSrc" format"reference"/><attr name"iconPaddingStart" format"dimension"/><attr name"iconPad…

访问网站显示不安全是什么原因?怎么解决?

访问网站时显示“不安全”&#xff0c;主要原因以及解决办法&#xff1a; 1.没用HTTPS加密&#xff1a;网站还在用老的HTTP协议&#xff0c;数据传输没加密&#xff0c;容易被人偷看或篡改。解决办法是网站管理员启用HTTPS&#xff0c;也就是给网站装个“SSL证书”。这个是最常…

模块十三 异常_Object

回顾与重点 模块十二回顾 权限修饰符&#xff1a; public → protected → 默认 → private a. 构造一般用public&#xff1a;便于new对象 b. 成员方法一般用public&#xff1a;便于调用 c. 属性一般用private&#xff1a;封装思想 final&#xff1a;最终的 a. 修饰类&#x…