06 - FFmpeg 提取 YUV420P 视频裸数据

---------------------------------------------------------------- YUV介绍 ----------------------------------------------------------------
> 概念:
 · YUV(也称YCbCr)是电视系统所采用的一种颜色编码方法。
其中"Y"表示明亮度(Luminance),也称灰阶值,它是基础信号;
而"U"和"V"表示的则是色度(Chrominance),它们的作用是描述影像的色彩及饱和度,用于指定像素的颜色,它俩都是被正交调制的。
 · "亮度"是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。"色度"则定义了颜色的两个方面――色调与饱和度,分别用Cr和Cb来表示。
其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异,而Cb反映的则是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
 · RGB类型,一幅图像的每个像素点由YUV三个分量构成。

> 特点:
 · 把亮度和色度分离,没有色度信息仍能显示完整的图像,只不过是黑白的,解决彩色电视跟黑白电视的兼容问题。
 · 与RGB视频信号传输相比,它最大的优点在于只需要占用极少的频宽(RGB要求“更多独立的视频信号同时传输)。

> YUV采样格式
主要采样格式:
 · YUV4:4:4 YUV三个信道的抽样率相同,在生成的图像中,每个像素占用3字节,每一个Y分量都对应一组UV分量。
 · YUV4:2:2 每个色差信道的抽样率是亮度的一半,在生成的图像中,每个由两个水平方向相邻的像素组成的宏像素占用4字节。每两个Y分量共用一组UV分量。
 · YUV4:2:0 每个色度分量,水平方向和竖直方向的抽样率都是2:1,色度的抽样率为4:1,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存,每四个Y分量共用一组UV分量。

> YUV存储格式
存储格式:
planner(平面)格式:先连续存储所有像素点的Y分量,紧接着存储所有像素点的U分量,最后是所有像素点的V分量。
packed(打包)格式:每个像素点的Y、U、V是连续交替存储的。

YUV420P中的Y、U、V分量都是平面格式,如下图所
YUV420SP中的Y分量为平面格式,UV分量为打包格式,即U和V分量交错排列。 

> YUV大小计算
RGB24:一张4x4像素的RGB图像大小为4x4x3=48字节。
YUV444:一张4x4像素的YUV444图像大小为4x4x3=48字节,跟RGB一样大。Y、U、V三个分量的大小都是4x4=16字节。
YUV422:一张4x4像素的YUV422图像大小为4x4x2=32字节,Y分量为全采样,即 4x4=16字节,U分量和V分量只有Y分量的一半,即U分量为4x4/2=8字节,V分量也是4x4/2=8字节。
YUV420:一张4x4像素的YUV420图像大小为4x4x(3/2)=24字节,Y分量为全采样,即4x4=16字节,U分量和V分量只有Y分量的四分之一,即U分量和V分量的大小4x4/4=4字节。
ffmpeg命令将图片转yuv数据:
    ffmpeg-i 200x200.png -s 200x200 -pix_fmt yuv420p yuv420p.yuv
> YUV Player 播放器播放:
> 大小计算:
    以YUV420P为例:200*200*3/2=60000 byte

方法1、h624 或者 mpeg2 提取 yuv420p 裸数据

#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFLT_THRESH 4096

int decodeVideoInterface(AVCodecContext *decoderCtx, AVPacket *packet, AVFrame *frame, FILE *dest_fp)
{
    int ret = avcodec_send_packet(decoderCtx, packet);
    if (ret == AVERROR(EAGAIN))
    {
        av_log(NULL, AV_LOG_WARNING, "[decodeAudioInterface] -- AVERROR(EAGAIN) \n");
    }
    else if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "send packet to decoder failed: %s\n", av_err2str(ret));
        return -1;
    }

    while (ret >= 0)
    {
        // 对于frame avcodec_receive_frame 内部每次都先调用
        ret = avcodec_receive_frame(decoderCtx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            return;
        }
        else if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "decode packet failed:%s\n", av_err2str(ret));
        }

        av_log(NULL, AV_LOG_INFO, "宽度: %uHZ, 高度:%u, 编码格式:%u \n", frame->width, frame->height, frame->format);

        fwrite(frame->data[0], 1, frame->width * frame->height, dest_fp);     // Y
        fwrite(frame->data[1], 1, frame->width * frame->height / 4, dest_fp); // U
        fwrite(frame->data[2], 1, frame->width * frame->height / 4, dest_fp); // V
    }
}

int decodeVideo(const char *inFileName, const char *outFileName)
{
    /************************************************************************************/
    // 打开输入文件
    FILE *inFile = fopen(inFileName, "rb");
    if (!inFile)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open:%s\n", inFileName);
        goto _end;
    }

    // 打开输出文件
    FILE *outFile = fopen(outFileName, "wb");
    if (!outFile)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open:%s\n", inFileName);
        goto _end;
    }

    uint8_t inFileDatabuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *inFileData = inFileDatabuf;
    size_t dataSize = fread(inFileData, 1, AUDIO_INBUF_SIZE, inFile);
    /************************************************************************************/

    enum AVCodecID VideoCodecID = AV_CODEC_ID_AAC;
    if (strstr(inFileName, "264") != NULL)
        VideoCodecID = AV_CODEC_ID_H264;
    else if (strstr(inFileName, "mpeg2") != NULL)
        VideoCodecID = AV_CODEC_ID_MPEG2VIDEO;
    else
        av_log(NULL, AV_LOG_WARNING, "default codec id:%d\n", VideoCodecID);

    // 查找解码器
    const AVCodec *decoder;
    decoder = avcodec_find_decoder(VideoCodecID);
    if (!decoder)
    {
        av_log(NULL, AV_LOG_ERROR, "decoder not found\n");
        goto _end;
    }
    // 获得裸流的解析器 -- 根据制定的解码器ID初始化相应裸流的解析器
    AVCodecParserContext *parserCtx = av_parser_init(decoder->id);
    if (!parserCtx)
    {
        av_log(NULL, AV_LOG_ERROR, "parserCtx not found\n");
        goto _end;
    }
    AVCodecContext *decoderCtx = NULL;
    decoderCtx = avcodec_alloc_context3(decoder);
    if (!decoderCtx)
    {
        av_log(NULL, AV_LOG_ERROR, "Conld not allocate video codec context\n");
        goto _end;
    }

    // 将解码器和解码器上下文进行关联
    int ret = avcodec_open2(decoderCtx, decoder, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open codec\n");
        goto _end;
    }

    AVFrame *decodeFrame = av_frame_alloc();
    if (decodeFrame == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate video decodeFrame\n");
        goto _end;
    }

    AVPacket *packet = NULL;
    packet = av_packet_alloc();
    while (dataSize > 0)
    {
        ret = av_parser_parse2(parserCtx, decoderCtx, &packet->data, &packet->size, inFileData, dataSize, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Error while parsing\n");
            goto _end;
        }
        inFileData += ret; // 跳过已经解析的数据
        dataSize -= ret;   // 对应的缓存大小也做相应的减小

        if (packet->size)
            decodeVideoInterface(decoderCtx, packet, decodeFrame, outFile);

        if (dataSize < AUDIO_REFLT_THRESH) // 如果数据少了则再次读取
        {
            memmove(inFileDatabuf, inFileData, dataSize); // 把之前剩的数据拷贝到 buffer 的其实位置
            inFileData = inFileDatabuf;
            // 读取数据 长度: AUDIO_INBUF_SIZE - dataSize
            int len = fread(inFileData + dataSize, 1, AUDIO_INBUF_SIZE - dataSize, inFile);
            if (len > 0)
                dataSize += len;
        }
    }

_end:
    if (outFile)
        fclose(outFile);
    if (inFile)
        fclose(inFile);
    if (decoderCtx)
        avcodec_free_context(&decoderCtx);
    if (parserCtx)
        av_parser_close(parserCtx);
    if (decodeFrame)
        av_frame_free(&decodeFrame);
    if (packet)
        av_packet_free(&packet);

    return 0;
}

===========================================================================

方法2 直接提取


int decodeVideoYUVInterface(AVCodecContext* codecCtx, AVPacket* packet, FILE* dest_fp, int* frameCount)
{
    int ret = avcodec_send_packet(codecCtx, packet);
    if (ret != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "send packet failed:%s\n", av_err2str(ret));
        return -1;
    }

    AVFrame* frame = av_frame_alloc();
    while (avcodec_receive_frame(codecCtx, frame) == 0)
    {
        fwrite(frame->data[0], 1, codecCtx->width * codecCtx->height, dest_fp);
        fwrite(frame->data[1], 1, codecCtx->width * codecCtx->height / 4, dest_fp);
        fwrite(frame->data[2], 1, codecCtx->width * codecCtx->height / 4, dest_fp);
        (*frameCount)++;
        av_log(NULL, AV_LOG_DEBUG, "frameCount:%d | linesize[0] = %d, linesize[1] = %d, linesize[2] = %d, width = %d, heigth = %d\n",
            *frameCount, frame->linesize[0], frame->linesize[1], frame->linesize[2], codecCtx->width, codecCtx->height);
    }
    if (frame)
    {
        av_frame_free(&frame);
    }
    return 0;
}

int decodeVideoYUV(const char* inFileName, const char* outFileName)
{
    int frameCount = 0;
    /**********************************************************************************/
    FILE* dest_fp = fopen(outFileName, "wb+");
    if (dest_fp == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "open outfile:%s failed!\n", outFileName);
        return -1;
    }
    /**********************************************************************************/
    AVFormatContext* inFmtCtx = NULL;
    int ret = avformat_open_input(&inFmtCtx, inFileName, NULL, NULL);
    if (ret != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "open input file:%s failed:%s\n", inFileName, av_err2str(ret));
        return -1;
    }

    ret = avformat_find_stream_info(inFmtCtx, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "find input stream info error:%s \n", av_err2str(ret));
        goto fail;
    }

    int videoStreamIndex = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoStreamIndex < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "find input bast stream index failed:%s\n", av_err2str(videoStreamIndex));
        goto fail;
    }

    // 创建解码器上下文
    AVCodecContext* codecCtx = avcodec_alloc_context3(NULL);
    if (codecCtx == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "alloc avcodec context failed!\n");
        goto fail;
    }

    // 拷贝编码参数
    avcodec_parameters_to_context(codecCtx, inFmtCtx->streams[videoStreamIndex]->codecpar);

    // 在当前的ffmpeg版本中看看有没有这个解码器
    AVCodec* decoder = avcodec_find_decoder(codecCtx->codec_id);
    if (decoder == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "find decoder failed, codec_id:%d\n", codecCtx->codec_id);
        ret = -1;
        goto fail;
    }

    // 找到后打开
    ret = avcodec_open2(codecCtx, decoder, NULL);
    if (ret != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "open decoder failed:%s \n", av_err2str(ret));
        goto fail;
    }

    AVPacket packet;
    av_init_packet(&packet);
    while (av_read_frame(inFmtCtx, &packet) >= 0)
    {
        if (packet.stream_index == videoStreamIndex)
        {
            if (decodeVideoYUVInterface(codecCtx, &packet, dest_fp, &frameCount) == -1)
            {
                ret = -1;
                av_packet_unref(&packet);
                goto fail;
            }
        }
        av_packet_unref(&packet);
    }
    // flush decoder
    decodeVideoYUVInterface(codecCtx, NULL, dest_fp, &frameCount);
fail:
    if (inFmtCtx)
    {
        avformat_close_input(&inFmtCtx);
    }
    if (codecCtx)
    {
        avcodec_free_context(&codecCtx);
    }
    if (dest_fp)
    {
        fclose(dest_fp);
    }

    return ret;
}

相关推荐

  1. 06 - FFmpeg 提取 YUV420P 视频数据

    2024-07-17 17:18:01       17 阅读
  2. 07 - FFmpeg 更改视频分辨率 - 保存 yuv420p

    2024-07-17 17:18:01       18 阅读
  3. 05 - FFmpeg 提取 PCM 音频数据

    2024-07-17 17:18:01       17 阅读
  4. c yuv422yuv420p

    2024-07-17 17:18:01       48 阅读
  5. Qt 中如何将图片转化为yuv420p

    2024-07-17 17:18:01       50 阅读
  6. 视频实战---从音视频文件中提取h264

    2024-07-17 17:18:01       37 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 17:18:01       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 17:18:01       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 17:18:01       45 阅读
  4. Python语言-面向对象

    2024-07-17 17:18:01       55 阅读

热门阅读

  1. 识别视频中的人数并统计出来

    2024-07-17 17:18:01       21 阅读
  2. 超详细Python教程——异步任务和定时任务

    2024-07-17 17:18:01       19 阅读
  3. opencv—常用函数学习_“干货“_7

    2024-07-17 17:18:01       19 阅读
  4. Nginx

    2024-07-17 17:18:01       18 阅读
  5. 大模型日报 2024-07-15

    2024-07-17 17:18:01       17 阅读
  6. 使用 CSS 实现透明效果

    2024-07-17 17:18:01       17 阅读
  7. HTML常见标签(一)

    2024-07-17 17:18:01       19 阅读
  8. 网络编程:IO多路复用(五个IO模型)

    2024-07-17 17:18:01       19 阅读
  9. 【防抖工具库 es-toolkit】

    2024-07-17 17:18:01       20 阅读