首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

如何通过libavformat将H264流复用成MP4文件

基础概念

libavformat 是 FFmpeg 项目中的一个库,用于处理多媒体文件的格式解析和封装。H.264 是一种视频编码标准,广泛用于高清视频传输和存储。MP4 是一种多媒体容器格式,可以包含视频、音频、字幕等多种媒体流。

相关优势

  1. 兼容性:MP4 格式在各种设备和播放器上都有很好的兼容性。
  2. 灵活性:可以包含多种媒体流,并且可以灵活地添加或删除音轨、字幕等。
  3. 压缩效率:H.264 编码在保证视频质量的同时,能够有效减少文件大小。

类型

  • 视频流:H.264 编码的视频流。
  • 音频流:通常为 AAC 编码的音频流。
  • 字幕流:可选的 SRT 或其他格式的字幕流。

应用场景

  • 视频会议记录。
  • 在线视频流媒体服务。
  • 移动设备上的视频播放。

实现步骤

以下是一个简单的示例代码,展示如何使用 libavformat 将 H.264 流复用成 MP4 文件:

代码语言:txt
复制
#include <stdio.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>

int main(int argc, char *argv[]) {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *video_dec_ctx = NULL, *video_enc_ctx = NULL;
    AVStream *video_st = NULL;
    AVPacket pkt;
    int ret, i;

    // 初始化 libavformat 和注册所有编解码器、格式和协议
    av_register_all();

    // 打开输入文件
    if ((ret = avformat_open_input(&fmt_ctx, "input.h264", NULL, NULL)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", argv[1]);
        goto end;
    }

    // 查找流信息
    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }

    // 查找视频流
    for (i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_st = fmt_ctx->streams[i];
            break;
        }
    }

    if (!video_st) {
        fprintf(stderr, "Could not find video stream in the input, aborting\n");
        ret = -1;
        goto end;
    }

    // 获取解码器参数并打开解码器
    AVCodec *video_dec = avcodec_find_decoder(video_st->codecpar->codec_id);
    if (!video_dec) {
        fprintf(stderr, "Failed to find decoder for stream #%u\n", video_st->index);
        ret = AVERROR_DECODER_NOT_FOUND;
        goto end;
    }

    video_dec_ctx = avcodec_alloc_context3(video_dec);
    if (!video_dec_ctx) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ret = avcodec_parameters_to_context(video_dec_ctx, video_st->codecpar);
    if (ret < 0) {
        fprintf(stderr, "Failed to copy decoder parameters to input context\n");
        goto end;
    }

    if ((ret = avcodec_open2(video_dec_ctx, video_dec, NULL)) < 0) {
        fprintf(stderr, "Failed to open decoder for stream #%u\n", video_st->index);
        goto end;
    }

    // 创建输出格式上下文
    AVOutputFormat *out_fmt = av_guess_format(NULL, "output.mp4", NULL);
    if (!out_fmt) {
        fprintf(stderr, "Could not deduce output format from file extension: using MPEG-4.\n");
        out_fmt = av_guess_format("mp4", NULL, NULL);
    }

    if (!out_fmt) {
        fprintf(stderr, "Could not find suitable output format\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    AVFormatContext *out_fmt_ctx = NULL;
    avformat_alloc_output_context2(&out_fmt_ctx, out_fmt, NULL, "output.mp4");
    if (!out_fmt_ctx) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    // 复制视频流
    AVCodec *video_enc = avcodec_find_encoder(out_fmt->video_codec);
    if (!video_enc) {
        fprintf(stderr, "Necessary encoder not found\n");
        ret = AVERROR_ENCODER_NOT_FOUND;
        goto end;
    }

    video_enc_ctx = avcodec_alloc_context3(video_enc);
    if (!video_enc_ctx) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    video_enc_ctx->width = video_dec_ctx->width;
    video_enc_ctx->height = video_dec_ctx->height;
    video_enc_ctx->sample_aspect_ratio = video_dec_ctx->sample_aspect_ratio;
    video_enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    video_enc_ctx->time_base = video_st->time_base;

    if (out_fmt->flags & AVFMT_GLOBALHEADER)
        video_enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    ret = avcodec_parameters_from_context(video_enc_ctx->codecpar, video_dec_ctx);
    if (ret < 0) {
        fprintf(stderr, "Could not copy the stream parameters\n");
        goto end;
    }

    ret = avcodec_open2(video_enc_ctx, video_enc, NULL);
    if (ret < 0) {
        fprintf(stderr, "Cannot open video encoder for stream #%u\n", video_st->index);
        goto end;
    }

    AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
    if (!out_stream) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ret = avcodec_parameters_copy(out_stream->codecpar, video_st->codecpar);
    if (ret < 0) {
        fprintf(stderr, "Failed to copy encoder parameters to output stream #%u\n", out_stream->index);
        goto end;
    }

    out_stream->time_base = video_enc_ctx->time_base;

    // 打开输出文件
    if (!(out_fmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&out_fmt_ctx->pb, "output.mp4", AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", "output.mp4");
            goto end;
        }
    }

    // 写入文件头
    ret = avformat_write_header(out_fmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }

    // 读取输入文件并写入输出文件
    while (1) {
        ret = av_read_frame(fmt_ctx, &pkt);
        if (ret < 0)
            break;

        if (pkt.stream_index == video_st->index) {
            ret = avcodec_send_packet(video_dec_ctx, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error sending a packet for decoding\n");
                goto end;
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(video_dec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    break;
                else if (ret < 0) {
                    fprintf(stderr, "Error during decoding\n");
                    goto end;
                }

                frame->pts = frame->pkt_dts;

                ret = avcodec_send_frame(video_enc_ctx, frame);
                if (ret < 0) {
                    fprintf(stderr, "Error sending a frame for encoding\n");
                    goto end;
                }

                while (ret >= 0) {
                    ret = avcodec_receive_packet(video_enc_ctx, &out_pkt);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        break;
                    else if (ret < 0) {
                        fprintf(stderr, "Error during encoding\n");
                        goto end;
                    }

                    out_pkt.stream_index = out_stream->index;
                    ret = av_interleaved_write_frame(out_fmt_ctx, &out_pkt);
                    if (ret < 0) {
                        fprintf(stderr, "Error while writing output packet\n");
                        goto end;
                    }
                    av_packet_unref(&out_pkt);
                }
            }
        }
        av_packet_unref(&pkt);
    }

    // 写入文件尾
    av_write_trailer(out_fmt_ctx);

end:
    av_packet_unref(&pkt);
    if (video_dec_ctx)
        avcodec_close(video_dec_ctx);
    if (video_enc_ctx)
        avcodec_close(video_enc_ctx);
    if (fmt_ctx && !(fmt_ctx->oformat->flags & AVFMT_NOFILE))
        avio_closep(&fmt_ctx->pb);
    if (out_fmt_ctx && !(out_fmt->flags & AVFMT_NOFILE))
        avio_closep(&out_fmt_ctx->pb);
    avformat_free_context(fmt_ctx);
    avformat_free_context(out_fmt_ctx);

    return ret < 0;
}

参考链接

常见问题及解决方法

  1. 编码器未找到
    • 确保 FFmpeg 编译时包含了所需的编码器。
    • 检查编码器名称是否正确。
  • 解码器未找到
    • 确保 FFmpeg 编译时包含了所需的解码器。
    • 检查解码器名称是否正确。
  • 文件打开失败
    • 确保输入文件存在且可读。
    • 确保输出文件路径可写。
  • 编码或解码错误
    • 检查输入数据的格式是否正确。
    • 确保编码和解码参数一致。

通过以上步骤和示例代码,你可以将 H.264 流复用成 MP4 文件。如果遇到具体问题,请根据错误信息进行排查和解决。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

领券