ffmpeg 分离视频中的YUV分量

概览

本文借用ffmpeg库, 把视频拆分为Y、U、V三个分量进行保存,可以使用YUV视频播放器分别播放。同时把视频中的其中某一帧图像保存下来, 也拆分为Y、U、V三个分量。让大家对YUV颜色空间有更一步的认识。
本文需要用到YUV播放器,没有的朋友可以在此下载:视频处理相关工具 提取码:pkxh

程序

本程序参考了雷神的相关文章, 同时解决了很多函数已经被ffmpeg 舍弃的问题,谨在此感谢雷神为我辈留下的宝贵学习资源。以下程序运行环境:
Win10, QT :5.12.3 ,ffmpeg: 4.2.1
视频资源:我下载了华为品牌的主题曲 dream it possible, 然后用potplayer 随便截取了一段

#include <iostream>
#ifdef __cplusplus
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}
#else
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#endif
using namespace std;

#define VEDIO_NAME "C:\\ZLJ\\Stuff\\vedio\\111.mkv"
/*the files defined below will be used to restore the Y/U/V component  vedio respectly*/
#define DEST_VEDIO_NAME_Y "C:\\ZLJ\\Stuff\\vedio\\dest_y.yuv"
#define DEST_VEDIO_NAME_U "C:\\ZLJ\\Stuff\\vedio\\dest_u.yuv"
#define DEST_VEDIO_NAME_V "C:\\ZLJ\\Stuff\\vedio\\dest_v.yuv"
/*the files below will be used to restore the YUV components of one frame*/
#define DEST_IMAG_NAME_YUV "C:\\ZLJ\\Stuff\\vedio\\dest_img_yuv.jpg"
#define DEST_IMAG_NAME_Y "C:\\ZLJ\\Stuff\\vedio\\dest_img_y.jpg"
#define DEST_IMAG_NAME_U "C:\\ZLJ\\Stuff\\vedio\\dest_img_u.jpg"
#define DEST_IMAG_NAME_V "C:\\ZLJ\\Stuff\\vedio\\dest_img_v.jpg"

int main()
{
    AVFormatContext *pFormatCtx;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVFrame         *pFrame, *pFrameYUV;
    AVPacket        *pPacket;
    int rst;
    size_t YSize = 0;
    FILE * fpYuv_y = nullptr;
    FILE * fpYuv_u = nullptr;
    FILE * fpYuv_v = nullptr;

    FILE * fpYuvImg_yuv = nullptr;
    FILE * fpYuvImg_y = nullptr;
    FILE * fpYuvImg_u = nullptr;
    FILE * fpYuvImg_v = nullptr;
    avformat_network_init();

    pFormatCtx  = avformat_alloc_context();
    rst = avformat_open_input(&pFormatCtx, VEDIO_NAME, nullptr, nullptr);
    if (rst == 0) {
        cout << "avformat_open_input invoke successfully" << endl;
    }
    else {
        return -1;
    }

    rst = avformat_find_stream_info(pFormatCtx, nullptr);
    if (rst < 0) {
        cout << "find stream infor failed " << endl;
        return -1;
    }

    unsigned int i = pFormatCtx->nb_streams + 1, index = 0;
    for(i = 0; i < pFormatCtx->nb_streams; ++i) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            index = i;
            break;
        }
    }
    if (i > pFormatCtx->nb_streams) {
        cout << "There has no vedio stream in the media file" << endl;
        return -1;
    }

    pCodec = avcodec_find_decoder(pFormatCtx->streams[index]->codecpar->codec_id);
    pCodecCtx = avcodec_alloc_context3(pCodec);
    avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[index]->codecpar);

    cout << pCodecCtx->width << "height = " << pCodecCtx->height << endl;
    int pixFmt = pFormatCtx->streams[index]->codecpar->format;
    rst = avcodec_open2(pCodecCtx, pCodec, nullptr);
    if (rst != 0) {
        cout << "open the codec failed" << endl;
        return -1;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();

    unsigned char *outBuffer = nullptr;

    outBuffer = static_cast<unsigned char *>(av_malloc(static_cast<unsigned long long>(av_image_get_buffer_size(static_cast<AVPixelFormat>(pixFmt), pCodecCtx->width, pCodecCtx->height, 1))));
    if (outBuffer == nullptr) {
        cout << "avmalloc failed " << endl;
        return -1;
    }
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, static_cast<AVPixelFormat>(pixFmt), pCodecCtx->width, pCodecCtx->height, 1);


    pPacket = static_cast<AVPacket *>(av_malloc(sizeof(AVPacket)));

    av_dump_format(pFormatCtx, static_cast<int>(index), VEDIO_NAME, 0);

    struct SwsContext *imgConvertCtx;

    imgConvertCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
    if (imgConvertCtx == nullptr) {
        cout << "failed to creat a imgConvertCtx"  << endl;
    }

    fpYuv_y = fopen(DEST_VEDIO_NAME_Y, "wb+");
    fpYuv_u = fopen(DEST_VEDIO_NAME_U, "wb+");
    fpYuv_v = fopen(DEST_VEDIO_NAME_V, "wb+");

    fpYuvImg_yuv = fopen(DEST_IMAG_NAME_YUV, "wb+");
    fpYuvImg_y = fopen(DEST_IMAG_NAME_Y, "wb+");
    fpYuvImg_u = fopen(DEST_IMAG_NAME_U, "wb+");
    fpYuvImg_v = fopen(DEST_IMAG_NAME_V, "wb+");

    i = 0;
    while(av_read_frame(pFormatCtx, pPacket) >= 0) {
        if (pPacket->stream_index == static_cast<int>(index)) {
            //            avcodec_decode_video2(pCodecCtx, pFrame, &gotPictuer, pPacket);
            avcodec_send_packet(pCodecCtx, pPacket);
            rst = avcodec_receive_frame(pCodecCtx, pFrame);
        }

        if (rst == 0) {
            //           cout << "successfully get a frame" << endl;
            sws_scale(imgConvertCtx, static_cast<const unsigned char * const *>(pFrame->data), pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

            YSize = static_cast<size_t>(pFrame->height * pFrame->width);
            /*the  YUV vedio component restored here*/
            fwrite(pFrameYUV->data[0], 1, YSize, fpYuv_y) ;
            fwrite(pFrameYUV->data[1], 1, YSize/4, fpYuv_u);
            fwrite(pFrameYUV->data[2], 1, YSize/4, fpYuv_v);
            i++;
            /*Why i == 600, rather than other number?   answer: its a random number, just inorder to dump a frame data*/
            if (i == 600) {
                /*the  YUV Frame component restored here*/
                fwrite(pFrameYUV->data[0], 1, YSize, fpYuvImg_y) ;
                fwrite(pFrameYUV->data[1], 1, YSize/4, fpYuvImg_u);
                fwrite(pFrameYUV->data[2], 1, YSize/4, fpYuvImg_v);
                fwrite(pFrameYUV->data[0], 1, YSize, fpYuvImg_yuv) ;
                fwrite(pFrameYUV->data[1], 1, YSize/4, fpYuvImg_yuv);
                fwrite(pFrameYUV->data[2], 1, YSize/4, fpYuvImg_yuv);
            }
        } else {
            cout << "failed to decode" << endl;
        }
        av_packet_unref(pPacket);
    }
    fclose(fpYuv_y);
    fclose(fpYuv_u);
    fclose(fpYuv_v);
    fclose(fpYuvImg_yuv);
    fclose(fpYuvImg_y);
    fclose(fpYuvImg_y);
    fclose(fpYuvImg_y);

    sws_freeContext(imgConvertCtx);
    av_frame_free(&pFrame);
    av_frame_free(&pFrameYUV);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    cout << "Hello World!" << endl;
    return 0;
}

运行效果

由于视频不好上传,运行结果就不在此展示, 朋友们可以自行配置运行环境看一下, 在这里只把截取的某一帧结果展示如下
原视频帧:1280X720

the original Frame
Y 分量:1280X720
Y component
U 分量:640X360
U component
V 分量 640X360
V component

释疑

question 1:Y、U、V分量的图片是怎么显示的?
answer: 我们在程序中把Y、U、V分量分别保存到文件中, 其实不管什么看图软件都不知道这里面保存的是什么玩意,这时候就需要借助YUV播放器了, 因为YUV播放器可以单独的解析某一个分量进行显示。
question 2:为什么 Y分量的分辨率和原图一样, U、V分量宽高都不一样?
answer: YUV420P 编码格式每四个Y公用一组UV分量。详情请参看我的另一篇文章YUV色彩空间浅析
question 3: 为什么我的图像/视频显示/播放不出来, 或者明显UV分量就不对?
answer: 结合上一个问答。YUV播放器,必须自己手动设置分辨率和色彩模式来帮助播放器正确解释YUV文件。当播放U、V的时候,需要把分辨率设置成原图的一半, 同时设置为color设置为Y(让播放器按照单一分量来解读文件)。这里面的道理请大家仔细体会。
question 4: 为什么用的是YUV420p, 而不是YUV422, YUV444?
answer: pix format信息保存再AVCodecContext 中, 我们可以在此获得, 同时再AVCodecParameters 里面也有保存, 大家需要根据自己的例程来具体考虑。
PS:question 4其实在程序中已经有体现, 但是我在看别的前辈的博客时候并没有解释pix format 是怎么来的, 容易给初学者造成困惑,特在此指出来。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值