经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 网络多媒体 » 查看文章
FFmpeg+SDL实时解码和渲染H264视频流
来源:cnblogs  作者:kason  时间:2023/7/12 10:58:53  对本文有异议

前言

之前实现了Android手机摄像头数据的TCP实时传输,今天接着聊聊,如何在PC端把接收到的H264视频流实时解码并渲染出来。这次使用的语言是C++,框架有FFmpeg和SDL2。

解码

解码部分使用FFmpeg,首先,需要初始化H264解码器:

  1. int H264Decoder::init() {
  2. codec = avcodec_find_decoder(AV_CODEC_ID_H264);
  3. if (codec == nullptr) {
  4. printf("No H264 decoder found\n");
  5. return -1;
  6. }
  7. codecCtx = avcodec_alloc_context3(codec);
  8. codecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
  9. if (avcodec_open2(codecCtx, codec, nullptr) < 0) {
  10. printf("Failed to open codec\n");
  11. return -2;
  12. }
  13. packet = av_packet_alloc();
  14. m_Frame = av_frame_alloc();
  15. parser = av_parser_init(AV_CODEC_ID_H264);
  16. return 0;
  17. }

然后,使用创建TCP连接到我们的Android端,读取数据包:

  1. bool read_data(SOCKET socket, void* data, unsigned int len) {
  2. while (len > 0) {
  3. int ret = recv(socket, (char*)data, len, 0);
  4. if (ret <= 0) {
  5. return false;
  6. }
  7. len -= ret;
  8. data = (char*)data + ret;
  9. }
  10. return true;
  11. }
  12. bool read_int(SOCKET socket, ULONG* value) {
  13. bool ret = read_data(socket, value, 4);
  14. if (ret) {
  15. *value = ntohl(*value);
  16. }
  17. return ret;
  18. }
  19. int PacketReceiver::readPacket(unsigned char** data, unsigned long* size) {
  20. ULONG pkgSize = 0;
  21. bool ret = read_int(m_Socket, &pkgSize);
  22. if (!ret) {
  23. printf("Failed to read packet size\n");
  24. return -1;
  25. }
  26. if (m_DataLen < pkgSize) {
  27. if (m_Data != nullptr) {
  28. delete[] m_Data;
  29. }
  30. m_Data = new unsigned char[pkgSize];
  31. m_DataLen = pkgSize;
  32. }
  33. if (!read_data(m_Socket, m_Data, pkgSize)) {
  34. printf("Failed to read packet data\n");
  35. return -2;
  36. }
  37. *data = m_Data;
  38. *size = pkgSize;
  39. return 0;
  40. }

再把每个数据包传送给H264解码器解码

  1. int H264Decoder::decode(unsigned char* data, int size, AVFrame** frame) {
  2. int new_pkg_ret = av_new_packet(packet, size);
  3. if (new_pkg_ret != 0) {
  4. printf("Failed to create new packet\n");
  5. return -1;
  6. }
  7. memcpy(packet->data, data, size);
  8. int ret = avcodec_send_packet(codecCtx, packet);
  9. if (ret < 0 && ret != AVERROR(EAGAIN)) {
  10. printf("Failed to parse packet\n");
  11. return -1;
  12. }
  13. ret = avcodec_receive_frame(codecCtx, m_Frame);
  14. if (ret == AVERROR(EAGAIN)) {
  15. *frame = nullptr;
  16. return 0;
  17. }
  18. if (ret != 0) {
  19. printf("Failed to read frame\n");
  20. return -1;
  21. }
  22. *frame = m_Frame;
  23. av_packet_unref(packet);
  24. return 0;
  25. }

解码器解码后,最终得到的是AVFrame对象,代表一帧画面,数据格式一般为YUV格式(跟编码端选择的像素格式有关)。

渲染

通过使用SDL2,我们可以直接渲染YUV数据,无需手动转成RGB。

首先,我们先初始化SDL2并创建渲染窗口:

  1. int YuvRender::init(int video_width, int video_height) {
  2. SDL_Init(SDL_INIT_VIDEO);
  3. SDL_Rect bounds;
  4. SDL_GetDisplayUsableBounds(0, &bounds);
  5. int winWidth = video_width;
  6. int winHeight = video_height;
  7. if (winWidth > bounds.w || winHeight > bounds.h) {
  8. float widthRatio = 1.0 * winWidth / bounds.w;
  9. float heightRatio = 1.0 * winHeight / bounds.h;
  10. float maxRatio = widthRatio > heightRatio ? widthRatio : heightRatio;
  11. winWidth = int(winWidth / maxRatio);
  12. winHeight = int(winHeight / maxRatio);
  13. }
  14. SDL_Window* window = SDL_CreateWindow(
  15. "NetCameraViewer",
  16. SDL_WINDOWPOS_UNDEFINED,
  17. SDL_WINDOWPOS_UNDEFINED,
  18. winWidth,
  19. winHeight,
  20. SDL_WINDOW_OPENGL
  21. );
  22. m_Renderer = SDL_CreateRenderer(window, -1, 0);
  23. m_Texture = SDL_CreateTexture(
  24. m_Renderer,
  25. SDL_PIXELFORMAT_IYUV,
  26. SDL_TEXTUREACCESS_STREAMING,
  27. video_width,
  28. video_height
  29. );
  30. m_VideoWidth = video_width;
  31. m_VideoHeight = video_height;
  32. m_Rect.x = 0;
  33. m_Rect.y = 0;
  34. m_Rect.w = winWidth;
  35. m_Rect.h = winHeight;
  36. return 0;
  37. }

每次解码出一帧画面的时候,再调用render函数渲染:

  1. int YuvRender::render(unsigned char* data[], int pitch[]) {
  2. int uvHeight = m_VideoHeight / 2;
  3. int ySize = pitch[0] * m_VideoHeight;
  4. int uSize = pitch[1] * uvHeight;
  5. int vSize = pitch[2] * uvHeight;
  6. int buffSize = ySize + uSize + vSize;
  7. if (m_FrameBufferSize < buffSize) {
  8. if (m_FrameBuffer != nullptr) {
  9. delete[] m_FrameBuffer;
  10. }
  11. m_FrameBuffer = new unsigned char[buffSize];
  12. m_FrameBufferSize = buffSize;
  13. }
  14. SDL_memcpy(m_FrameBuffer, data[0], ySize);
  15. SDL_memcpy(m_FrameBuffer + ySize, data[1], uSize);
  16. SDL_memcpy(m_FrameBuffer + ySize + uSize, data[2], vSize);
  17. SDL_UpdateTexture(m_Texture, NULL, m_FrameBuffer, pitch[0]);
  18. SDL_RenderClear(m_Renderer);
  19. SDL_RenderCopy(m_Renderer, m_Texture, NULL, &m_Rect);
  20. SDL_RenderPresent(m_Renderer);
  21. SDL_PollEvent(&m_Event);
  22. if (m_Event.type == SDL_QUIT) {
  23. exit(0);
  24. }
  25. return 0;
  26. }

性能

在搭载AMD Ryzen 5 5600U的机器上,1800 x 1350的分辨率,解码一帧平均25ms, 渲染1~2ms,加上编码和传输延时,总体延时在70ms左右。

完整源码已上传至Github: https://github.com/kasonyang/net-camera/tree/main/viewer-app

原文链接:https://www.cnblogs.com/kason/p/17543091.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号