FFmpeg 解码视频文件

使用FFmpeg库(版本号为:4.4.2-0ubuntu0.22.04.1)解码媒体文件中的视频流为YUV(YUV420P)数据文件。可以使用我上传的MP4文件测试,就是解码出来数据有点大,有20.8 GB😂。YUV查看工具: YUViewicon-default.png?t=N7T8https://github.com/IENT/YUView/releases

或者使用ffplay查看,命令如下:

# `-f rawvideo`:指定输入格式为原始视频数据。
# `-pixel_format yuv420p`:指定像素格式为 YUV420p。
# `-video_size 1920x1080`:指定视频的分辨率为 1920x1080。
# `-framerate 60`:指定视频的帧率为 60 fps。
# `input.yuv`:YUV 文件的名称。
ffplay -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 60 input.yuv

代码如下:

#include <stdio.h>
#include "libavformat/avformat.h"
#include <libavutil/imgutils.h>
 
int main(int argc, char *argv[])
{
    int ret = -1;
    const char *input_filePath = argv[1];
    const char *output_filePath = argv[2];
    AVFormatContext *ifmt_ctx = NULL;
    AVCodec *codec = NULL;
    AVPacket *pkt = NULL;
    AVFrame *frame = NULL;
    AVCodecContext *codec_ctx = NULL;
 
    if (argc < 3)
    {
        printf("Usage: %s <input file> <output file>\n", argv[0]);
        return -1;
    }

    // 打印ffmpeg版本信息
    printf("ffmpeg version: %s\n", av_version_info());
    // 打开输入的流
    if (avformat_open_input(&ifmt_ctx, input_filePath, NULL, NULL) != 0)
    {
        printf("avformat_open_input failed\n");
        return -1;
    }
    // 查找流信息
    if (avformat_find_stream_info(ifmt_ctx, NULL) < 0)
    {
        printf("avformat_find_stream_info failed\n");
        goto end;
    }
    // 寻找视频流索引和解码器
    int video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
    if (video_index < 0)
    {
        printf("av_find_best_stream failed\n");
        goto end;
    }
 
    // 打印输入流信息
    printf("输入流信息:\n");
    av_dump_format(ifmt_ctx, video_index, input_filePath, 0);
    printf("\n");
 
    // 初始化解码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        printf("avcodec_alloc_context3 failed\n");
        goto end;
    }
    // 将流的解码器参数拷贝到解码器上下文
    if (avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[video_index]->codecpar) < 0)
    {
        printf("avcodec_parameters_to_context failed\n");
        goto end;
    }
    // 打开解码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
        printf("avcodec_open2 failed\n");
        goto end;
    }
    pkt = av_packet_alloc();
    if (!pkt)
    {
        printf("av_packet_alloc failed\n");
        goto end;
    }
    frame = av_frame_alloc();
    if (!frame)
    {
        printf("av_frame_alloc failed\n");
        goto end;
    }
    // 打开输出文件
    FILE *foutput = fopen(output_filePath, "wb");
    if (!foutput)
    {
        printf("fopen failed\n");
        goto end;
    }
 
    // 计算一张图片的大小
    int size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1);
    uint8_t *yuvbuf = (uint8_t *)av_malloc(size);
    if (!yuvbuf)
    {
        printf("av_malloc failed\n");
        goto end;
    }
    /* 从输入文件中读取数据包 */
    // 调用 av_read_frame 成功后需要调用 av_packet_unref 释放数据包
    while ((ret = av_read_frame(ifmt_ctx, pkt)) >= 0)
    {
        if (pkt->stream_index == video_index)
        {
            // 发送数据包到解码器
            int val = avcodec_send_packet(codec_ctx, pkt);
            if (val >= 0)
            {
                // 从解码器中接收解码后的数据
                val = avcodec_receive_frame(codec_ctx, frame);
                if (val == AVERROR(EAGAIN) || val == AVERROR_EOF)
                {
                    av_packet_unref(pkt);
                    continue;
                }
                else if (val < 0)
                {
                    printf("avcodec_receive_frame error (errmsg '%s')\n", av_err2str(val));
                    av_packet_unref(pkt);
                    goto end;
                }
                if (codec_ctx->pix_fmt == AV_PIX_FMT_YUV420P)
                {
                    // 将YUV数据拷贝到yuvbuf中
 
                    // Y分量的大小 = w * h,frame->linesize[0] = 每一行的Y分量大小 = w
                    memcpy(yuvbuf, frame->data[0], frame->linesize[0] * frame->height);
 
                    // U分量的大小 = w / 2 * h / 2,frame->linesize[1] = 每一行的U分量大小 = w / 2
                    memcpy(yuvbuf + frame->linesize[0] * frame->height, frame->data[1],
                           frame->linesize[1] * frame->height / 2);
 
                    // V分量的大小 = w / 2 * h / 2,frame->linesize[2] = 每一行的V分量大小 = w / 2
                    memcpy(yuvbuf + frame->linesize[0] * frame->height + frame->linesize[1] * frame->height / 2,
                           frame->data[2], frame->linesize[2] * frame->height / 2);
 
                    // 写入文件
                    fwrite(yuvbuf, 1, size, foutput);
                }
            }
        }
        av_packet_unref(pkt);
    }
    if (ret < 0 && ret != AVERROR_EOF)
    {
        printf("av_read_frame error (errmsg '%s')\n", av_err2str(ret));
        goto end;
    }
 
    // 冲刷解码器
    avcodec_send_packet(codec_ctx, NULL);
    while (1)
    {
        int val = avcodec_receive_frame(codec_ctx, frame);
        if (val == AVERROR_EOF)
        {
            break;
        }
        else if (val < 0)
        {
            printf("avcodec_receive_frame error (errmsg '%s')\n", av_err2str(val));
            goto end;
        }
        if (val >= 0)
        {
            if (codec_ctx->pix_fmt == AV_PIX_FMT_YUV420P)
            {
                for (int i = 0; i < frame->height; i++)
                {
                    memcpy(yuvbuf + i * frame->linesize[0], frame->data[0] + frame->linesize[0] * i, frame->linesize[0]);
                }
                for (int i = 0; i < frame->height / 2; i++)
                {
                    memcpy(yuvbuf + frame->width * frame->height + i * frame->linesize[1],
                           frame->data[1] + frame->linesize[1] * i, frame->linesize[1]);
                }
                for (int i = 0; i < frame->height / 2; i++)
                {
                    memcpy(yuvbuf + frame->width * frame->height * 5 / 4 + i * frame->linesize[2],
                           frame->data[2] + frame->linesize[2] * i, frame->linesize[2]);
                }
                fwrite(yuvbuf, 1, size, foutput);
            }
        }
    }
 
end:
    if (yuvbuf)
        av_free(yuvbuf);
    if (foutput)
        fclose(foutput);
    if (frame)
        av_frame_free(&frame);
    if (pkt)
        av_packet_free(&pkt);
    if (codec_ctx)
        avcodec_free_context(&codec_ctx);
 
    avformat_close_input(&ifmt_ctx);
 
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值