首页 > 其他分享 >FFmpeg+SDL实时解码和渲染H264视频流

FFmpeg+SDL实时解码和渲染H264视频流

时间:2023-07-11 09:55:42浏览次数:50  
标签:return FFmpeg H264 视频流 packet int ret SDL data

前言

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

解码

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

int H264Decoder::init() {
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (codec == nullptr) {
        printf("No H264 decoder found\n");
        return -1;
    }
    codecCtx = avcodec_alloc_context3(codec);
    codecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
    if (avcodec_open2(codecCtx, codec, nullptr) < 0) {
        printf("Failed to open codec\n");
        return -2;
    }
    packet = av_packet_alloc();
    m_Frame = av_frame_alloc();
    parser = av_parser_init(AV_CODEC_ID_H264);
    return 0;
}

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

bool read_data(SOCKET socket, void* data, unsigned int len) {
    while (len > 0) {
        int ret = recv(socket, (char*)data, len, 0);
        if (ret <= 0) {
            return false;
        }
        len -= ret;
        data = (char*)data + ret;
    }
    return true;
}

bool read_int(SOCKET socket, ULONG* value) {
    bool ret = read_data(socket, value, 4);
    if (ret) {
        *value = ntohl(*value);
    }
    return ret;
}

int PacketReceiver::readPacket(unsigned char** data, unsigned long* size) {
    ULONG pkgSize = 0;
    bool ret = read_int(m_Socket, &pkgSize);
    if (!ret) {
        printf("Failed to read packet size\n");
        return -1;
    }
    if (m_DataLen < pkgSize) {
        if (m_Data != nullptr) {
            delete[] m_Data;
        }
        m_Data = new unsigned char[pkgSize];
        m_DataLen = pkgSize;
    }
    if (!read_data(m_Socket, m_Data, pkgSize)) {
        printf("Failed to read packet data\n");
        return -2;
    }
    *data = m_Data;
    *size = pkgSize;
    return 0;
}

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

int H264Decoder::decode(unsigned char* data, int size, AVFrame** frame) {
    int new_pkg_ret = av_new_packet(packet, size);
    if (new_pkg_ret != 0) {
        printf("Failed to create new packet\n");
        return -1;
    }
    memcpy(packet->data, data, size);
    int ret = avcodec_send_packet(codecCtx, packet);
    if (ret < 0 && ret != AVERROR(EAGAIN)) {
        printf("Failed to parse packet\n");
        return -1;
    }
    ret = avcodec_receive_frame(codecCtx, m_Frame);
    if (ret == AVERROR(EAGAIN)) {
        *frame = nullptr;
        return 0;
    }
    if (ret != 0) {
        printf("Failed to read frame\n");
        return -1;
    }
    *frame = m_Frame;
    av_packet_unref(packet);
    return 0;
}

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

渲染

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

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

int YuvRender::init(int video_width, int video_height) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Rect bounds;
    SDL_GetDisplayUsableBounds(0, &bounds);
    int winWidth = video_width;
    int winHeight = video_height;
    if (winWidth > bounds.w || winHeight > bounds.h) {
        float widthRatio = 1.0 * winWidth / bounds.w;
        float heightRatio = 1.0 * winHeight / bounds.h;
        float maxRatio = widthRatio > heightRatio ? widthRatio : heightRatio;
        winWidth = int(winWidth / maxRatio);
        winHeight = int(winHeight / maxRatio);
    }
    SDL_Window* window = SDL_CreateWindow(
        "NetCameraViewer",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        winWidth,
        winHeight,
        SDL_WINDOW_OPENGL
    );
    m_Renderer = SDL_CreateRenderer(window, -1, 0);
    m_Texture = SDL_CreateTexture(
        m_Renderer,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        video_width,
        video_height
    );
    m_VideoWidth = video_width;
    m_VideoHeight = video_height;
    m_Rect.x = 0;
    m_Rect.y = 0;
    m_Rect.w = winWidth;
    m_Rect.h = winHeight;
    return 0;
}

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

int YuvRender::render(unsigned char* data[], int pitch[]) {
    int uvHeight = m_VideoHeight / 2;
    int ySize = pitch[0] * m_VideoHeight;
    int uSize = pitch[1] * uvHeight;
    int vSize = pitch[2] * uvHeight;
    int buffSize =  ySize + uSize + vSize;
    if (m_FrameBufferSize < buffSize) {
        if (m_FrameBuffer != nullptr) {
            delete[] m_FrameBuffer;
        }
        m_FrameBuffer = new unsigned char[buffSize];
        m_FrameBufferSize = buffSize;
    }
    SDL_memcpy(m_FrameBuffer, data[0], ySize);
    SDL_memcpy(m_FrameBuffer + ySize, data[1], uSize);
    SDL_memcpy(m_FrameBuffer + ySize + uSize, data[2], vSize);
    SDL_UpdateTexture(m_Texture, NULL, m_FrameBuffer, pitch[0]);
    SDL_RenderClear(m_Renderer);
    SDL_RenderCopy(m_Renderer, m_Texture, NULL, &m_Rect);
    SDL_RenderPresent(m_Renderer);
    SDL_PollEvent(&m_Event);
    if (m_Event.type == SDL_QUIT) {
        exit(0);
    }
    return 0;
}

性能

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

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

标签:return,FFmpeg,H264,视频流,packet,int,ret,SDL,data
From: https://www.cnblogs.com/kason/p/17543091.html

相关文章

  • ffmpeg
    ffmpeg目录ffmpeg0.ffprobe0.1获取流列表0.2获取流信息0.3所有编码分组0.4统计帧数0.5所有帧0.6像素格式0.7选择流0.8打印格式1.ffmpeg命令基本形式2.流选择2.1从多个文件中选择特定的流2.2屏蔽所有视频流2.3屏蔽所有音频流3.时长3.1从某刻开始一定时间3.2从某......
  • golang 使用ffmpeg工具实现音视频转码
    1ffmpeg工具是什么FFmpeg即是一款音视频编解码工具,同时也是一组音视频编码开发套件,作为编码开发套件,它为开发者提供了丰富的音视频处理的调用接口。FFmpeg提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议的流媒体、多种多彩格式转换、多种采样率转换、多种码率转换......
  • ffmpeg指定屏幕区域录屏
    ffmpeg-hide_banner-loglevelerror-fgdigrab-show_region1-framerate6-video_size1914x930-offset_x1921-offset_y105-idesktop-pix_fmtyuv420pout6.mp4 -loglevelerror:只显示错误日志-video_size1914x930-offset_x1921-offset_y105:指定录屏......
  • python opencv无法编码h264、opencv编码的mp4视频无法在网页中播放
    pythonopencv无法编码h264、opencv编码的mp4视频无法在网页中播放,这好像是因为开源许可的协议不同,导致pythonopencv中没有内置h264的编码,无法以h264的格式保存视频。所以我就直接使用webm格式的视频:output_path='output_video.webm'output_codec=cv2.VideoWriter_fourcc......
  • ffmpeg编码中的一些问题
    1.在查看设备支持的dshow设备时出现:[dshow@00000286dc5e7e40]Couldnotenumeratevideodevices(ornonefound).解决:下载screencapturerecorder并安装(github上有),然后就可以正常使用。2.avformat_open_input打开输入设备时报错返回码为-5?源码如下:extern"C"{#in......
  • web js 播放rtsp视频流方案
    场景需要在web端预览海康无线摄像头视频流,所以采用海康自身提供的websdk无法使用方案1rtsp流推送到应用服务器,应用服务器再通过ffmpeg推送到nginx,js再去拉流缺点:多了一层转发,造成了一定的延迟方案2通过webRTC方案,使用现有开源插件webrtc-streamerhttps://github......
  • FFmpeg 已支持动画 JPEG-XL
    导读除了Apple宣布在其Safari浏览器中支持JPEG-XL图像格式之外,FFmpeg也宣布现已支持解码动画JPEG-XL文件。去年以来,FFmpeg已经能够使用libjxl库解码静态JPEG-XL图像,随着本周在FFmpeg6.1发布之前合并的提交,现在它也能够处理动画/多帧JPEG-XL内容。......
  • 离线安装ffmpeg源码包【详细教程】
    今天分享一下ffmpeg源码包的安装过程,针对在没有网络环境下,且不能直接使用yum如何成功安装ffmpeg源码包。博主本人通过正式服务器测试,记录整个安装过程。值得大家收藏同时,我会分享一下如何使用ffmpeg对H.264格式视频(MP4)进行m3u8+ts切片的转换,并生成m3u8+ts格式文件ffmpeg所需要环......
  • FFMPEG 在网络源关闭时保持连接
      FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的......
  • ffmpeg播放RTSP的一点优化
    简单记录一下最近使用ffmpeg播放RTSP做的一点参数优化。先做如下定义:AVDictionary*options=NULL;1.画质优化原生的ffmpeg参数在对1920x1080的RTSP流进行播放时,花屏现象很严重,根据网上查的资料,可以通过增大“buffer_size”参数来提高画质,减少花屏现象如:av_dict_set(&op......