如何使用FFmpeg库进行媒体文件的转封装操作

本文介绍了如何使用FFmpeg库进行媒体文件的转封装操作。注意: 转封装是一种将媒体文件的音视频流重新封装到新的文件中的操作,它通常用于更改媒体文件的容器格式,而不进行重新编码。

步骤1:安装FFmpeg

参见:FFmpeg编译安装,这篇文章不够详细,我现在对编译了解的更多了,后续出一篇详细的。

步骤2:包含FFmpeg头文件

如可以使用类似以下的代码包含头文件:

#include <libavformat/avformat.h> 
...

步骤3:初始化FFmpeg

在进行任何FFmpeg操作之前,需要初始化FFmpeg库。可以使用以下代码进行初始化(ffmpeg4.x后可以不再使用):

av_register_all(); 

步骤4:打开输入文件

使用avformat_open_input()函数打开,需要转封装的输入文件。如:

AVFormatContext* ifmt_ctx = nullptr;
if (avformat_open_input(&ifmt_ctx, input_filename, nullptr, nullptr) != 0) {
// 处理打开输入文件失败的情况
}

步骤5:获取流信息

使用avformat_find_stream_info()函数获取输入文件的流信息。如:

if (avformat_find_stream_info(ifmt_ctx, nullptr) < 0) {
// 处理获取流信息失败的情况
}

步骤6:创建输出文件上下文

使用avformat_alloc_output_context2()函数创建一个新的输出文件上下文。如:

AVFormatContext* ofmt_ctx = nullptr;
if (avformat_alloc_output_context2(&ofmt_ctx, nullptr, nullptr, output_filename) < 0) {
// 处理创建输出文件上下文失败的情况
}

步骤7:将输入文件的音视频流添加到输出文件

使用avformat_new_stream()函数为每个输入文件的音视频流创建一个对应的输出流。然后,将输入文件的音视频流参数复制到输出流中。如:

for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream* in_stream = ifmt_ctx->streams[i];
AVStream* out_stream = avformat_new_stream(ofmt_ctx, nullptr);
if (out_stream == nullptr) {
// 处理创建新输出流失败的情况
continue;
}

if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0) {
// 处理复制编码参数失败的情况
continue;
}
}

步骤8:打开输出文件

使用avio_open()函数打开输出文件。如:

if (avio_open(&ofmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
// 处理打开输出文件失败的情况
}

步骤9:写入输出文件头部信息

使用avformat_write_header()函数写入输出文件的头部信息。以下是一个示例代码片段:

if (avformat_write_header(ofmt_ctx, nullptr) < 0) {
// 处理写入头部信息失败的情况
}

步骤10:转封装音视频包

使用av_interleaved_write_frame()函数将输入文件的音视频包转封装到输出文件中。以下是一个示例代码片段:

AVPacket packet;
while (av_read_frame(ifmt_ctx, &packet) >= 0) {
// 设置音视频包的流索引
packet.stream_index = stream_mapping[packet.stream_index];

// 将音视频包写入输出文件
if (av_interleaved_write_frame(ofmt_ctx, &packet) < 0) {
// 处理写入音视频包失败的情况
break;
}
av_packet_unref(&packet);
}

请注意,stream_mapping是一个数组,用于映射输入文件的流索引到输出文件的流索引。

步骤11:写入输出文件尾部信息

使用av_write_trailer()函数写入输出文件的尾部信息。以下是一个示例代码片段:

if (av_write_trailer(ofmt_ctx) < 0) {
// 处理写入尾部信息失败的情况
}

步骤12:释放资源

在完成转封装操作后,记得释放分配的资源。以下是一些示例代码片段:

avformat_close_input(&ifmt_ctx);
avformat_free_context(ofmt_ctx);

总结

通过按照上述步骤使用FFmpeg库进行转封装操作,可以将一个媒体文件的音视频流重新封装到一个新的文件中,而不进行重新编码。

转封装完整代码示例基于对以上转封装的流程,我写了一份转封装的代码供大家参考,你可以在终端使用”程序名 *.mp4 *.flv”的命令将一个mp4视频文件转成一个flv视频文件,当然也可以转为其它格式的视频文件。

头文件部分:

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
struct AVFormatContext;
struct AVPacket;
struct AVOutputFormat;

class Remuxer {
public:
Remuxer();
~Remuxer();

int MediaRemux(const std::string& input_filename, const std::string& output_filename);
private:
int OpenInputFile(const std::string& input_filename, AVFormatContext** ifmt_ctx);
int FindStreamInfo(AVFormatContext* ifmt_ctx);
void DumpInputFormat(AVFormatContext* ifmt_ctx, const std::string& input_filename);
int CreatOutputContext(const std::string& output_filename, AVFormatContext** ofmt_ctx);
int InitStreamMapping(AVFormatContext* ifmt_ctx, int** stream_mapping, int* stream_mapping_size);
int AddOutputStreams(AVFormatContext* ifmt_ctx, AVFormatContext* ofmt_ctx, int* stream_mapping);
void DumpOutputContext(AVFormatContext* ofmt_ctx, const std::string& output_filename);
int OpenOutputFile(AVFormatContext* ofmt_ctx, const std::string& output_filename);
int WriteHeader(AVFormatContext* ofmt_ctx);
int RemuxPackets(AVFormatContext* ifmt_ctx, AVFormatContext* ofmt_ctx, int* stream_mapping, int stream_mapping_size);
void WriteTrailer(AVFormatContext* ofmt_ctx);
private:
AVFormatContext *ifmt_ctx;
AVFormatContext *ofmt_ctx;
AVPacket *pkt;
int *stream_mapping;
const AVOutputFormat *ofmt;
};

源文件部分

#include "remux.h"

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/mem.h>
#include <libavutil/timestamp.h>
}


Remuxer::Remuxer() : ifmt_ctx(nullptr), ofmt_ctx(nullptr), 
        pkt(nullptr), stream_mapping(nullptr)
{}

Remuxer::~Remuxer()
{
    if (pkt)
    {
        av_packet_free(&pkt);
    }
    if (ifmt_ctx)
    {
        avformat_close_input(&ifmt_ctx);
    }
    
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
    {
        avio_closep(&ofmt_ctx->pb);
    }

    if (ofmt_ctx) 
    {
        avformat_free_context(ofmt_ctx);
    }
    av_freep(&stream_mapping);

}


int Remuxer::MediaRemux(const std::string &input_filename, const std::string &output_filename)
{
    AVFormatContext* ifmt_ctx = nullptr;
    AVFormatContext* ofmt_ctx = nullptr;
    int *stream_mapping = nullptr;
    int stream_mapping_size = 0;

    int ret = OpenInputFile(input_filename, &ifmt_ctx);
    if (ret < 0) return ret;

    ret = FindStreamInfo(ifmt_ctx);
    if (ret < 0) 
    {
        avformat_close_input(&ifmt_ctx);
        return ret;
    }

    DumpInputFormat(ifmt_ctx, input_filename); // 打印输入视频信息

    ret = CreatOutputContext(output_filename, &ofmt_ctx);
    if (ret < 0) 
    {
        avformat_close_input(&ifmt_ctx);
        return ret;
    }

    ret = InitStreamMapping(ifmt_ctx, &stream_mapping, &stream_mapping_size);
    if (ret < 0)
    {
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ofmt_ctx);
        return ret;
    }
    ret = AddOutputStreams(ifmt_ctx, ofmt_ctx, stream_mapping);

    if (ret < 0) {
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ofmt_ctx);
        av_freep(&stream_mapping);
        return ret;
    }

    DumpOutputContext(ofmt_ctx, output_filename);

    ret = OpenOutputFile(ofmt_ctx, output_filename);
    if (ret < 0) {
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ofmt_ctx);
        av_freep(&stream_mapping);
        return ret;
    }

    ret = WriteHeader(ofmt_ctx);
    if (ret < 0) {
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ofmt_ctx);
        av_freep(&stream_mapping);
        return ret;
    }

    ret = RemuxPackets(ifmt_ctx, ofmt_ctx, stream_mapping, stream_mapping_size);
    if (ret < 0) {
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ofmt_ctx);
        av_freep(&stream_mapping);
        return ret;
    }

    WriteTrailer(ofmt_ctx);

    avformat_close_input(&ifmt_ctx);
    avformat_free_context(ofmt_ctx);
    av_freep(&stream_mapping);

    return 0;
}

int Remuxer::OpenInputFile(const std::string& input_filename, AVFormatContext** ifmt_ctx)
{
    int ret = avformat_open_input(ifmt_ctx, input_filename.c_str(), 0, 0);
    if (ret < 0)
    {
        fprintf(stderr, "Colud not open input file '%s'\n", input_filename.c_str());
    }
    return ret;
}


int Remuxer::FindStreamInfo(AVFormatContext* ifmt_ctx)
{
    int ret = avformat_find_stream_info(ifmt_ctx, 0);
    if (ret < 0)
    {
        fprintf(stderr, "Failed to retrieve input stream information\n");
    }
    return ret;
}

void Remuxer::DumpInputFormat(AVFormatContext* ifmt_ctx, const std::string& input_filename)
{
    av_dump_format(ifmt_ctx, 0, input_filename.c_str(), 0);
}

int Remuxer::CreatOutputContext(const std::string &output_filename, AVFormatContext** ofmt_ctx)
{
    int ret = avformat_alloc_output_context2(ofmt_ctx, NULL, NULL, output_filename.c_str());
    if (!(*ofmt_ctx))
    {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
    }
    return ret;
}

int Remuxer::InitStreamMapping(AVFormatContext* ifmt_ctx, int** stream_mapping, int* stream_mapping_size)
{
    *stream_mapping_size = ifmt_ctx->nb_streams;
    *stream_mapping = (int*)av_calloc(*stream_mapping_size, sizeof(**stream_mapping));
    if (!(*stream_mapping)) {
        return AVERROR(ENOMEM);
    }
    return 0;
}

int Remuxer::AddOutputStreams(AVFormatContext* ifmt_ctx, AVFormatContext* ofmt_ctx, int* stream_mapping)
{
    int stream_idx = 0;
    AVOutputFormat *ofmt = ofmt_ctx->oformat;
    for (int i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        AVStream *out_stream;
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVCodecParameters *in_codecapr = in_stream->codecpar;

        if (in_codecapr->codec_type != AVMEDIA_TYPE_AUDIO &&
            in_codecapr->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_codecapr->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_mapping[i] = -1;
            continue;
        }
        stream_mapping[i] = stream_idx++;

        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream)
        {
            fprintf(stderr, "Failed allocating output stream\n");
            return AVERROR_UNKNOWN;
        }

        int ret = avcodec_parameters_copy(out_stream->codecpar, in_codecapr);
        if (ret < 0)
        {
            fprintf(stderr, "Failed to copy codec parameters\n");
            return ret;
        }
        out_stream->codecpar->codec_tag = 0;
    }
    return 0;
}

void Remuxer::DumpOutputContext(AVFormatContext* ofmt_ctx, const std::string& output_filename)
{
    av_dump_format(ofmt_ctx, 0, output_filename.c_str(), 1);
}
int Remuxer::OpenOutputFile(AVFormatContext* ofmt_ctx, const std::string& output_filename)
{
    int ret = 0;
    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx->pb, output_filename.c_str(), AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            fprintf(stderr, "Could not open output file '%s'\n", output_filename.c_str());
        }
    }
    return ret;
}

int Remuxer::WriteHeader(AVFormatContext* ofmt_ctx)
{
    int ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0)
    {
        fprintf(stderr, "Error occurred when opening output file\n");
    }
    return ret;
}

int Remuxer::RemuxPackets(AVFormatContext* ifmt_ctx, AVFormatContext* ofmt_ctx, int* stream_mapping, int stream_mapping_size)
{
    AVPacket* pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Could not allocate AVPacket\n");
        return -1;
    }

    while (true) {
        int ret = av_read_frame(ifmt_ctx, pkt);
        if (ret < 0)
            break;

        if (pkt->stream_index >= stream_mapping_size ||
            stream_mapping[pkt->stream_index] == -1) {
            av_packet_unref(pkt);
            continue;
        }

        pkt->stream_index = stream_mapping[pkt->stream_index];
        AVStream* in_stream = ifmt_ctx->streams[pkt->stream_index];
        AVStream* out_stream = ofmt_ctx->streams[pkt->stream_index];

        /* copy packet */
        pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
        pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
        pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);
        pkt->pos = -1;

        ret = av_interleaved_write_frame(ofmt_ctx, pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_packet_unref(pkt);
    }

    av_packet_free(&pkt);
    return 0;
}

void Remuxer::WriteTrailer(AVFormatContext* ofmt_ctx)
{
    av_write_trailer(ofmt_ctx);
}

主函数部分

#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include "./remux_ex/remux.h"

int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]);
return 1;
}

const std::string input_filename = argv[1];
const std::string output_filename = argv[2];


Remuxer remuxer;
int ret = remuxer.MediaRemux(input_filename, output_filename);
if (ret != 0) {
fprintf(stderr, "Error occurred during remuxing\n");
return 1;
}
system("pause");
return 0;
}

本文到此结束,如有疑问可以留言或加我微信进行咨询。

如何使用FFmpeg库进行媒体文件的转封装操作

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论