利用FFmpge进行视频解码(从H264视频流到图像)

class Ffmpeg_Decoder
{
public:
    AVCodecParserContext *avParserContext;
    AVPacket avpkt;            //数据包结构体
    AVFrame *m_pRGBFrame;    //帧对象
    AVFrame *m_pYUVFrame;    //帧对象
    AVCodec *pCodecH264;    //解码器
    AVCodecContext *c;        //解码器数据结构对象
    uint8_t *yuv_buff;      //yuv图像数据区
    uint8_t *rgb_buff;        //rgb图像数据区
    SwsContext *scxt;        //图像格式转换对象
    uint8_t *filebuf;        //读入文件缓存
    uint8_t *outbuf;        //解码出来视频数据缓存
    int nDataLen;            //rgb图像数据区长度

    IplImage* img;            //OpenCV图像显示对象

    uint8_t *pbuf;            //用以存放帧数据
    int nOutSize;            //用以记录帧数据长度
    int haveread;            //用以记录已读buf长度
    int decodelen;            //解码器返回长度
    int piclen;                //解码器返回图片长度
    int piccount;            //输出图片计数


public:
    void Ffmpeg_Decoder_Init();//初始化
    void Ffmpeg_Decoder_Show(AVFrame *pFrame, int width, int height);//显示图片
    void Ffmpeg_Decoder_Close();//关闭
};
对类中声明的三个函数进行定义


void Ffmpeg_Decoder::Ffmpeg_Decoder_Init()
{
    avcodec_register_all();     //注册编解码器
    av_init_packet(&avpkt);     //初始化包结构
    m_pRGBFrame = new AVFrame[1];//RGB帧数据赋值
    m_pYUVFrame = avcodec_alloc_frame();
    filebuf = new uint8_t[1024 * 1024];//初始化文件缓存数据区
    pbuf = new uint8_t[200 * 1024];//初始化帧数据区
    yuv_buff = new uint8_t[200 * 1024];//初始化YUV图像数据区
    rgb_buff = new uint8_t[1024 * 1024];//初始化RGB图像帧数据区
    pCodecH264 = avcodec_find_decoder(CODEC_ID_H264);     //查找h264解码器
    if (!pCodecH264)
    {
        fprintf(stderr, "h264 codec not found\n");
        exit(1);
    }
    avParserContext = av_parser_init(CODEC_ID_H264);
    if (!pCodecH264)return;
    c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放
    
    if (pCodecH264->capabilities&CODEC_CAP_TRUNCATED)
        c->flags |= CODEC_FLAG_TRUNCATED;    /* we do not send complete frames */
    if (avcodec_open2(c, pCodecH264, NULL) < 0)return;
    nDataLen = 0;
}
void Ffmpeg_Decoder::Ffmpeg_Decoder_Show(AVFrame *pFrame, int width, int height)
{
    CvSize  rectangle_img_size;
    rectangle_img_size.height = height;
    rectangle_img_size.width = width;

    img = cvCreateImage(rectangle_img_size, IPL_DEPTH_8U, 3);
    uchar* imgdata = (uchar*)(img->imageData);     //图像的数据指针

    for (int y = 0; y<height; y++)
    {
        memcpy(imgdata + y*width * 3, pFrame->data[0] + y*pFrame->linesize[0], width * 3);
    }
    cvShowImage("解码图像", img);
    cvWaitKey(1);//可以将图像停留时间设的长点以便观察
    cvReleaseImage(&img);
    imgdata = NULL;
}
void Ffmpeg_Decoder::Ffmpeg_Decoder_Close()
{
    delete[]filebuf;
    delete[]pbuf;
    delete[]yuv_buff;
    delete[]rgb_buff;
    av_free(m_pYUVFrame);//释放帧资源
    avcodec_close(c);//关闭解码器
    av_free(c);
}
后面是主函数部分,因为通常解码都是从文件中读取数据流或者从网络中得到数据缓存,所以出于方便和操作性本人把解码的部分代码写在了主函数中

void main()
{
    Ffmpeg_Decoder ffmpegobj;
    ffmpegobj.Ffmpeg_Decoder_Init();//初始化解码器
    FILE *pf = NULL;
    fopen_s(&pf, "myData.h264", "rb");
    while (true)
    {
        ffmpegobj.nDataLen = fread(ffmpegobj.filebuf, 1, 1024 * 10, pf);//读取文件数据
        if (ffmpegobj.nDataLen<=0)
        {
            fclose(pf);
            break;
        }
        else
        {
            ffmpegobj.haveread = 0;
            while (ffmpegobj.nDataLen > 0)
            {
                int nLength = av_parser_parse2(ffmpegobj.avParserContext, ffmpegobj.c, &ffmpegobj.yuv_buff,
                    &ffmpegobj.nOutSize, ffmpegobj.filebuf + ffmpegobj.haveread, ffmpegobj.nDataLen, 0, 0, 0);//查找帧头
                ffmpegobj.nDataLen -= nLength;//查找过后指针移位标志
                ffmpegobj.haveread += nLength;
                if (ffmpegobj.nOutSize <= 0)
                {
                    continue;
                }
                ffmpegobj.avpkt.size = ffmpegobj.nOutSize;//将帧数据放进包中
                ffmpegobj.avpkt.data = ffmpegobj.yuv_buff;
                while (ffmpegobj.avpkt.size > 0)
                {
                    ffmpegobj.decodelen = avcodec_decode_video2(ffmpegobj.c, ffmpegobj.m_pYUVFrame, &ffmpegobj.piclen, &ffmpegobj.avpkt);//解码
                    if (ffmpegobj.decodelen < 0)
                    {
                        break;
                    }
                    if (ffmpegobj.piclen)
                    {
                        ffmpegobj.scxt = sws_getContext(ffmpegobj.c->width, ffmpegobj.c->height, ffmpegobj.c->pix_fmt, ffmpegobj.c->width, ffmpegobj.c->height, PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL);//初始化格式转换函数
                        if (ffmpegobj.scxt!= NULL)
                        {
                            avpicture_fill((AVPicture*)ffmpegobj.m_pRGBFrame, (uint8_t*)ffmpegobj.rgb_buff, PIX_FMT_RGB24, ffmpegobj.c->width, ffmpegobj.c->height);//将rgb_buff填充到m_pRGBFrame
                            if (avpicture_alloc((AVPicture *)ffmpegobj.m_pRGBFrame, PIX_FMT_RGB24, ffmpegobj.c->width, ffmpegobj.c->height) >= 0)
                            {
                                sws_scale(ffmpegobj.scxt, ffmpegobj.m_pYUVFrame->data, ffmpegobj.m_pYUVFrame->linesize, 0,
                                    ffmpegobj.c->height, ffmpegobj.m_pRGBFrame->data, ffmpegobj.m_pRGBFrame->linesize);
                                ffmpegobj.Ffmpeg_Decoder_Show(ffmpegobj.m_pRGBFrame, ffmpegobj.c->width, ffmpegobj.c->height);//解码图像显示
                            }
                            sws_freeContext(ffmpegobj.scxt);//释放格式转换器资源
                            avpicture_free((AVPicture *)ffmpegobj.m_pRGBFrame);//释放帧资源
                            av_free_packet(&ffmpegobj.avpkt);//释放本次读取的帧内存
                        }
                    }
                    ffmpegobj.avpkt.size -= ffmpegobj.decodelen;
                    ffmpegobj.avpkt.data += ffmpegobj.decodelen;
                }
            }
        }
    }
    ffmpegobj.Ffmpeg_Decoder_Close();//关闭解码器
}
OK,到此用FFmpeg进行视频解码的博客就告一段落了,下面是整个工程的下载地址:点击打开链接。同上一篇编码的博文工程,如果想要工程运行起来需要自行配置OpenCV。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在C#中使用FFmpeg播放RTSP H264视频流并将其显示到窗体上,你可以使用FFmpeg.AutoGen库来实现。以下是一个简单的示例代码: 首先,你需要在项目中引用FFmpeg.AutoGen库。你可以通过NuGet包管理器来安装它。 然后,你可以使用以下代码来实现播放RTSP视频流并将其显示到窗体上: ```csharp using System; using System.Drawing; using System.Windows.Forms; using FFmpeg.AutoGen; namespace RTSPPlayer { public partial class MainForm : Form { private AVFormatContext* _formatContext; private AVCodecContext* _codecContext; private AVFrame* _frame; private AVPacket _packet; private SwsContext* _swsContext; private IntPtr _videoFrameBuffer; private Bitmap _bitmap; private bool _isPlaying; public MainForm() { InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e) { // 初始化FFmpeg ffmpeg.av_register_all(); ffmpeg.avformat_network_init(); // 打开RTSP流 string rtspUrl = "your_rtsp_url"; AVDictionary* options = null; ffmpeg.av_dict_set(&options, "rtsp_transport", "tcp", 0); ffmpeg.av_dict_set(&options, "stimeout", "5000000", 0); int result = ffmpeg.avformat_open_input(&_formatContext, rtspUrl, null, &options); if (result != 0) { MessageBox.Show("无法打开RTSP流"); return; } // 查找视频流信息 result = ffmpeg.avformat_find_stream_info(_formatContext, null); if (result < 0) { MessageBox.Show("无法找到视频流信息"); return; } // 查找视频流索引 int videoStreamIndex = -1; for (int i = 0; i < _formatContext->nb_streams; i++) { if (_formatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { MessageBox.Show("无法找到视频流"); return; } // 获取视频解码器 AVCodec* codec = ffmpeg.avcodec_find_decoder(_formatContext->streams[videoStreamIndex]->codecpar->codec_id); if (codec == null) { MessageBox.Show("无法找到视频解码器"); return; } // 创建解码器上下文 _codecContext = ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(_codecContext, _formatContext->streams[videoStreamIndex]->codecpar); // 打开解码器 result = ffmpeg.avcodec_open2(_codecContext, codec, null); if (result < 0) { MessageBox.Show("无法打开视频解码器"); return; } // 创建帧对象 _frame = ffmpeg.av_frame_alloc(); // 创建包对象 ffmpeg.av_init_packet(&_packet); // 创建图像转换上下文 _swsContext = ffmpeg.sws_getContext(_codecContext->width, _codecContext->height, _codecContext->pix_fmt, _codecContext->width, _codecContext->height, AVPixelFormat.AV_PIX_FMT_BGR24, ffmpeg.SWS_BILINEAR, null, null, null); // 创建视频帧缓冲区 int bufferSize = ffmpeg.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_BGR24, _codecContext->width, _codecContext->height, 1); _videoFrameBuffer = ffmpeg.av_malloc((ulong)bufferSize); ffmpeg.av_image_fill_arrays(_frame->data, _frame->linesize, (byte*)_videoFrameBuffer, AVPixelFormat.AV_PIX_FMT_BGR24, _codecContext->width, _codecContext->height, 1); // 创建位图对象 _bitmap = new Bitmap(_codecContext->width, _codecContext->height, _codecContext->width * 3, PixelFormat.Format24bppRgb, _videoFrameBuffer); // 开始播放 _isPlaying = true; Play(); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { // 停止播放 _isPlaying = false; ffmpeg.av_packet_unref(&_packet); ffmpeg.av_frame_free(&_frame); ffmpeg.avcodec_close(_codecContext); ffmpeg.avformat_close_input(&_formatContext); ffmpeg.avformat_network_deinit(); ffmpeg.av_free(_videoFrameBuffer); ffmpeg.sws_freeContext(_swsContext); } private void Play() { while (_isPlaying) { // 读取一帧数据 int result = ffmpeg.av_read_frame(_formatContext, &_packet); if (result < 0) { // 读取失败,结束播放 _isPlaying = false; break; } if (_packet.stream_index == _formatContext->streams[0]->index) { // 解码视频帧 result = ffmpeg.avcodec_send_packet(_codecContext, &_packet); if (result < 0) { // 解码失败,跳过该帧 ffmpeg.av_packet_unref(&_packet); continue; } result = ffmpeg.avcodec_receive_frame(_codecContext, _frame); if (result < 0) { // 解码失败,跳过该帧 ffmpeg.av_packet_unref(&_packet); continue; } // 转换图像格式 ffmpeg.sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _codecContext->height, _frame->data, _frame->linesize); // 刷新窗体 pictureBox.Image = _bitmap; pictureBox.Refresh(); } // 释放数据包 ffmpeg.av_packet_unref(&_packet); } } } } ``` 请注意,上述代码仅为示例,你需要根据实际情况进行适当的修改和调整。 相关问题: 1. 如何在C#中使用FFmpeg播放RTSP视频流? 2. 如何在C#中显示视频帧到窗体上? 3. 如何解码H264视频流? 4. 如何使用FFmpeg.AutoGen库在C#中进行视频解码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值