FFmpeg——视频解码——转YUV并输出——av_image函数介绍


视频解码一般步驺

1.准备上下文
AVFormatContext
AVCodec AVCodecContext
2.准备上下文的原子对象
AVFrame
AVPacket
3.配置解码器
一种是直接find decoder输入AVCodecID即可
另一种是按AVFormatContext->streams[st_index_video]->codecpar->codec_id来find decoder
第二种比较保险,寻找格式方便,比如若解析txt文件,他会自动找到‘tty’格式的解码器,如果是我自己,一定很难找到的。这种方法根本上是根据后缀名判断的,若无后缀名则在format open input的时候就返回NULL了,这种情况下若自己对数据内容有把握,可以自己设定AVInputFormat,退一步说,你随便设置一个InputFormat,后面read_frame以后再用合适的方法来处理也是绝对可以的!(但此时format组件的功能函数就别用了,比如dump format之类的)
4.解码
大致没啥说的,都是send_packet然后recive_frame,或者decode_video/audio(据说这种方法将来会被放弃)。下面举个例子说明一下这种最简单粗暴的解码方法



MJPEG视频解码实例

0.原因
MJPEG的视频码流是可以直接播放的,所以解封装以后直接保存到文件即可,最简单
1.目的
保存MJPEG格式的码流到文件并播放
2.代码
环境: Qt 5.7.1 64位

#include 
#include 
#include 
#include 
#include 
extern "C" {
#include 
#include 
#include 
#include 
#include 
#include 
}

#define STR(str)    QString::fromLocal8Bit((char*)str)
#define LOG         qDebug()

#define INBUF_SIZE 4096
#define DST_FILENAME    QString("D:/fmt_avi/")


int main(int argc, char** argv)
{
    char* filename = "D:/fmt.avi";
    AVFormatContext* ic=0;
    AVCodec* c=0;
    AVCodecContext* cc=0;
    AVPacket* pkt=0;
    AVFrame* frame=0;
    int ret=-1;

    ic= avformat_alloc_context();
    if(avformat_open_input(&ic, filename, 0, 0) <0)
        exit(1);
    int st_idx= -1;

    for(int i=0; inb_streams; ++i)
        if(ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            st_idx= i;
            break;
        }
    if(-1 == st_idx){
        avformat_close_input(&ic);
        exit(1);
    }

    avformat_find_stream_info(ic, 0);
    av_dump_format(ic, 0, "", 0);

    c= avcodec_find_decoder((AVCodecID)ic->streams[st_idx]->codecpar->codec_id);
    cc= avcodec_alloc_context3(c);
    if(!cc){
        avformat_close_input(&ic);
        exit(1);
    }
    if(avcodec_open2(cc, c, 0) <0)
        goto end;

    pkt= av_packet_alloc();
    frame=av_frame_alloc();

    //!
    FILE* fp= fopen("D:/fmt.mjpg", "wb");
    //!

    int got_pict= 0;
    while(av_read_frame(ic, pkt) >=0){
        if(pkt->stream_index == st_idx){
            fwrite(pkt->data, pkt->size, 1, fp);
        }
    }

end:
    avformat_close_input(&ic);
    avcodec_free_context(&cc);
    av_packet_free(&pkt);
    av_frame_free(&frame);
    fclose(fp);

    return 0;
}

3.效果
用ffplay播放:ffplay -i D:/fmt.mjpg
FFmpeg——视频解码——转YUV并输出——av_image函数介绍_第1张图片






很多时候解码没这么简单,下面还以上面视频文件为例,对数据进行解码到YUV420P的像素格式并播放


视频解码为YUV420P并播放

1.简介及准备
视频流最终有两种去路,即RGB和YUV,YUV即灰度、色度、饱和度,要注意的是a*b大小的YUV图片其UV通道大小都只有(a*b)/2,原因在这里;其次一般的播放器无法直接播放YUV文件,需要下载YUV Player,做好以上工作以后,开始写代码。
2.代码

#include 
#include 
#include 
#include 
#include 
extern "C" {
#include 
#include 
#include 
#include 
#include 
#include 
}

#define STR(str)    QString::fromLocal8Bit((char*)str)
#define LOG         qDebug()

#define INBUF_SIZE 4096
#define DST_FILENAME    QString("D:/fmt_avi/")


int main(int argc, char** argv)
{
    char* filename = "D:/fmt.avi";
    AVFormatContext* ic=0;
    AVCodec* c=0;
    AVCodecContext* cc=0;
    AVPacket* pkt=0;
    AVFrame* frame=0;
    int ret=-1;



    ic= avformat_alloc_context();
    if(avformat_open_input(&ic, filename, 0, 0) <0)
        exit(1);
    int st_idx= -1;

    for(int i=0; inb_streams; ++i)
        if(ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            st_idx= i;
            break;
        }
    if(-1 == st_idx){
        avformat_close_input(&ic);
        exit(1);
    }

    avformat_find_stream_info(ic, 0);
    av_dump_format(ic, 0, "", 0);

    c= avcodec_find_decoder((AVCodecID)ic->streams[st_idx]->codecpar->codec_id);
    cc= avcodec_alloc_context3(c);
    if(!cc){
        avformat_close_input(&ic);
        exit(1);
    }
    if(avcodec_open2(cc, c, 0) <0)
        goto end;



    pkt= av_packet_alloc();
    frame=av_frame_alloc();
    AVFrame* frameYUV= av_frame_alloc();

    //!
    FILE* fp= fopen("D:/fmt_avi/stream.yuv", "wb");
//    if(!fp)
//        goto end;
    //

    AVCodecParameters* cpar= ic->streams[st_idx]->codecpar;
    av_image_alloc(frameYUV->data, frameYUV->linesize, cpar->width, cpar->height, AV_PIX_FMT_YUV420P, 1);
    SwsContext* swsc= sws_getContext(cpar->width, cpar->height, (AVPixelFormat)cpar->format,
                                     cpar->width, cpar->height, AV_PIX_FMT_YUV420P,
                                     SWS_FAST_BILINEAR, 0,0,0);
    int got_pict= 0;
    while(av_read_frame(ic, pkt) >=0){
        if(pkt->stream_index == st_idx){
            if(avcodec_decode_video2(cc, frame, &got_pict, pkt) >=0){
                if(got_pict){
                    ret= sws_scale(swsc,
                                   (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height,
                                   (uint8_t* const*)frameYUV->data, frameYUV->linesize);
                    if(ret != 0){
                        fwrite(frameYUV->data[0], cc->width*cc->height, 1, fp);
                        fwrite(frameYUV->data[1], cc->width*cc->height/4, 1, fp);
                        fwrite(frameYUV->data[2], cc->width*cc->height/4, 1, fp);
                        LOG << "write one frame" << cc->frame_number;
                        //
                    }
                }
            }
        }
    }

end:
    avformat_close_input(&ic);
    avcodec_free_context(&cc);
    av_packet_free(&pkt);
    av_frame_free(&frame);
    av_frame_free(&frameYUV);
    fclose(fp);
    sws_freeContext(swsc);
    av_free(&frameYUV->data[0]);

    return 0;
}

首先解码,然后转图像数据
我先用av_image_alloc分配目标YUV图像,再用sws (switch scale)内的sws_getContext获取格式转换组件,然后sws_scale转换图像,最终将frameYUV写入文件。

3.效果
FFmpeg——视频解码——转YUV并输出——av_image函数介绍_第2张图片

4.参考
没有大佬们的努力,我不可能进步这么快。
此处参考了雷的文章


FFmpeg Image组件介绍

参考
https://blog.csdn.net/liaozc/article/details/6110474
这里介绍了普遍的data和linesize的关系,RGB中只占第一行,YUV则占三行,长度比约为2:1:1,约的是padding size
https://blog.csdn.net/mydear_11000/article/details/50404084
这里介绍了YUV420,422,444格式的区别,对于写代码需要知道的是:
YUV420P 中UV通道为width/2, height/2
YUV422P中UV通道为width/2, height
YUV444P中UV通道为width, height
https://blog.csdn.net/lanxiaziyi/article/details/74347911
这里介绍了YUVJ和YUV的普遍区别


函数简介
比较简单,略。

你可能感兴趣的:(FFmpeg)