9-2_使用ffmpeg解码H264文件

使用ffmpeg解码H264文件

参考资料:

这一小节将分别演示使用命令和代码的方式实现H264文件解码生成YUV文件。

1.使用命令解码H264文件

执行这行命令会将yuv420p的H264文件转换为yuv文件。

ffmpeg -i input.h264 -pix_fmt yuv420p output.yuv

注意:执行前需要安装ffmpeg,并提前准备input.h264文件。

执行完成后会自动生成解码完成的output.yuv文件,使用yuvplayer软件打开,选择对应的分辨率和格式即可正常播放。

2.使用程序解码H264文件

我们使用程序进行H264的解码和前面命令用到的库是一样的,H264解码流程如下所示:

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

int main(int argc, char *argv[]) {
    //输入和输出文件名
    const char *input_filename = "input.h264";
    const char *output_filename = "output.yuv";

    av_register_all();// 注册所有的编解码器和格式

    AVFormatContext *format_ctx = nullptr;//存储文件格式信息
    //存储文件格式信息
    if (avformat_open_input(&format_ctx, input_filename, nullptr, nullptr) != 0) {
        fprintf(stderr, "Could not open input file.\n");
        return -1;
    }
    // 获取流信息
    if (avformat_find_stream_info(format_ctx, nullptr) < 0) {
        fprintf(stderr, "Could not find stream information.\n");
        return -1;
    }

    AVCodec *codec = nullptr;
    AVCodecContext *codec_ctx = nullptr;
    int video_stream_index = -1;

    // 查找视频流
    for (unsigned int i = 0; i < format_ctx->nb_streams; i++) {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            // 查找解码器
            codec = avcodec_find_decoder(format_ctx->streams[i]->codecpar->codec_id);
            // 申请编解码器参数
            codec_ctx = avcodec_alloc_context3(codec);
            //// 将查到编解码器参数复制出来
            avcodec_parameters_to_context(codec_ctx, format_ctx->streams[i]->codecpar);
            break;
        }
    }

    // 检查是否找到解码器
    if (codec == nullptr) {
        fprintf(stderr, "Could not find codec.\n");
        return -1;
    }

    // 打开编解码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
        fprintf(stderr, "Could not open codec.\n");
        return -1;
    }

    // 分配帧和包
    AVFrame *frame = av_frame_alloc();
    AVPacket packet;

    // 打开输出文件
    FILE *output_file = fopen(output_filename, "wb");

    //读取帧
    while (av_read_frame(format_ctx, &packet) >= 0) {
        // 检查是否是视频流
        if (packet.stream_index == video_stream_index) {
            // 发送包到解码器
            if (avcodec_send_packet(codec_ctx, &packet) == 0) {
                // 接收解码后的帧
                while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                    // 写入YUV数据到输出文件
                    fwrite(frame->data[0], 1, frame->linesize[0] * codec_ctx->height, output_file);
                    fwrite(frame->data[1], 1, frame->linesize[1] * codec_ctx->height / 2, output_file);
                    fwrite(frame->data[2], 1, frame->linesize[2] * codec_ctx->height / 2, output_file);
                }
            }
        }
         // 释放包
        av_packet_unref(&packet);
    }
    // 关闭输出文件
    fclose(output_file);
    // 释放帧
    av_frame_free(&frame);
    // 释放编解码器上下文
    avcodec_free_context(&codec_ctx);
    // 关闭输入文件
    avformat_close_input(&format_ctx);

    return 0;
}