FFmpeg分离(解封装)视频和音频

您所在的位置:网站首页 mp4设备支持什么格式的视频 FFmpeg分离(解封装)视频和音频

FFmpeg分离(解封装)视频和音频

2023-06-20 04:20| 来源: 网络整理| 查看: 265

使用FFmpeg库对mp4文件进行解封装,提取mp4中的视频流和音频流输出到单独的输出文件中。

所谓的分离视频和音频是我们通俗的说法,官方的说法叫解封装。与解封装对应的叫封装或复用器,也就是将多个视频流或音频流合并成一个多媒体文件就叫封装。

API及数据结构介绍

在FFmpeg中解封装的大致流程如下图所示:

ffmpeg解封装流程图

在这里需要注意的一个点是av_find_best_stream不一定能获取到你想要的流,比如你想通过av_find_best_stream获取音频流的索引,笔者开发中发现对于某些格式是无法获取成功的, 此时需要遍历一下解封装上下文的流,通过流的解码器类型来进行获取,例如你想要获取音频流,则可以判断解码器的类型是否是音频解码器即可。

下面介绍一下实现分离视频和音频数据所需要使用到的主要API以及相关的数据结构。

1、libavformat

libavformat库,是FFmpeg中用于处理各种媒体容器格式的库,它描述了一个媒体文件或媒体流的构成和基本信息,它的两个主要功能就是封装和解封装,可以说它是贯穿整个FFmpeg的根。

在解封装时,我们主要用到avformat中的几个函数avformat_alloc_context、avformat_open_input和avformat_close_input,其中avformat_open_input和avformat_close_input是 一对搭配使用的函数,一个打开一个关闭,千万不要忘记avformat_close_input,否则会发生内存泄漏。

2、AVPacket AVPacket类,用于存储编码后的帧数据。它一般由解封装导出,然后传递给解码器作为输入;又或者,从编码器作为输出,然后传递给封装去进行写入。

AVPacket可以表示一个视频包或者一个音频包,内部包含了这个视频包或音频包的播放时长,播放时间戳、二进制数据等相关信息。对于音视频等二进制数据,AVPacket内部使用了引用计数的方式进行数据共享。

对于AVPacket的那个字段,我们点进去头文件可以看到每个字段都有清晰的注释解析,这里就不细说了,例如:

typedef struct AVPacket { /** * A reference to the reference-counted buffer where the packet data is * stored. * May be NULL, then the packet data is not reference-counted. */ AVBufferRef *buf; /** * Presentation timestamp in AVStream->time_base units; the time at which * the decompressed packet will be presented to the user. * Can be AV_NOPTS_VALUE if it is not stored in the file. * pts MUST be larger or equal to dts as presentation cannot happen before * decompression, unless one wants to view hex dumps. Some formats misuse * the terms dts and pts/cts to mean something different. Such timestamps * must be converted to true pts/dts before they are stored in AVPacket. */ int64_t pts; /** * Decompression timestamp in AVStream->time_base units; the time at which * the packet is decompressed. * Can be AV_NOPTS_VALUE if it is not stored in the file. */ int64_t dts; uint8_t *data; int size; int stream_index; /** * A combination of AV_PKT_FLAG values */ int flags; /** * Additional packet data that can be provided by the container. * Packet can contain several types of side information. */ AVPacketSideData *side_data; int side_data_elems; /** * Duration of this packet in AVStream->time_base units, 0 if unknown. * Equals next_pts - this_pts in presentation order. */ int64_t duration; int64_t pos; ///< byte position in stream, -1 if unknown /** * for some private data of the user */ void *opaque; /** * AVBufferRef for free use by the API user. FFmpeg will never check the * contents of the buffer ref. FFmpeg calls av_buffer_unref() on it when * the packet is unreferenced. av_packet_copy_props() calls create a new * reference with av_buffer_ref() for the target packet's opaque_ref field. * * This is unrelated to the opaque field, although it serves a similar * purpose. */ AVBufferRef *opaque_ref; /** * Time base of the packet's timestamps. * In the future, this field may be set on packets output by encoders or * demuxers, but its value will be by default ignored on input to decoders * or muxers. */ AVRational time_base; } AVPacket;

下面是使用FFmpeg进行解封装的主要API调用:

avformat_alloc_context #封装结构体分配内存 // 可以不调用,avformat_open_input会判断入参是否为NULL,自行分配 avformat_open_input #打开输入文件用于读取数据 av_find_best_stream#获取流信息 针对每个stream处理 - pFormatContext->nb_streams - avcodec_find_decoder #根据流中的编码参数AVCodecParameters,查找是否支持该编码 - 判断流的类型 pLocalCodecParameters->codec_type - 保存AVCodecParameters和AVCodec,用于后续处理 av_read_frame #读取一包AVPacket数据包 提取视频

在FFMpeg中一般mp4解封装提取到的H264裸流是不带start code的,也就是提取到的这种H264裸流不能使用ffplay直接播放,还好FFmpeg很贴心地给我们提供了一个h264_mp4toannexb过滤器,通过这个过滤器我们可以很方便地 给提取到的H264加上start code,从而能让ffplay直接播放。

废话少说,直接上代码:

AVFormatContext *avFormatContext = nullptr; AVPacket *avPacket = nullptr; AVFrame *avFrame = nullptr; FILE *h264_out = nullptr; FILE *audio_out = nullptr; AVBSFContext *bsf_ctx = nullptr; void init_h264_mp4toannexb(AVCodecParameters *avCodecParameters) { if (nullptr == bsf_ctx) { const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb"); // 2 初始化过滤器上下文 av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext; // 3 添加解码器属性 avcodec_parameters_copy(bsf_ctx->par_in, avCodecParameters); av_bsf_init(bsf_ctx); } } void MediaDeMuxerCore::de_muxer_video(std::string media_path, std::string out_video_path) { // 分配上下文 avFormatContext = avformat_alloc_context(); // 打开输入文件 avformat_open_input(&avFormatContext, media_path.c_str(), nullptr, nullptr); // 获取视频流索引 int video_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (video_index < 0) { std::cout size, h264_out); av_packet_unref(avPacket); //减少引用计数 } } else { av_packet_unref(avPacket); //减少引用计数 } } // 刷 fflush(h264_out); } avformat_close_input(&avFormatContext); } 提取音频

对于FFmpeg中解封装的音频AAC文件来说,mp4文件解封装出来的音频不附带adts头信息的,但是笔者看到有资料说对于ts格式的话好像解封装出来又是带有adts头的(笔者这个没有验证过)。对于这些没有附带adts头信息的aac音频文件,ffplay也是无法直接播放的,因此我们在提取音频信息时需要手动加上adts头信息。

针对添加adts头信息的话我们有两种方式,一种是对对adts比较熟悉的,可以在每个音频包的前面增加7个或者9个字节即可。还有一种就是使用FFmpeg的复用器封装功能,让其自动加上adts头信息。

其中使用FFmpeg内部复用器封装的步骤如下:

1、调用 av_guess_format 让ffmpeg帮你找到一个合适的文件格式。

2、调用 avformat_new_stream 为输出文件创建一个新流。3、调用 avio_open 打开新创建的文件。4、调用 avformat_write_header 写文件头。5、调用 av_interleaved_write_frame 写文件内容。6、调用 av_write_trailer 写文件尾。7、调用 avio_close 关闭文件

后面的代码笔者两种方式都简单测试了一下,提取到的aac音频文件都可以正常播放。

下面贴一下全部代码:

MediaDeMuxerCore.h #include class MediaDeMuxerCore { public: MediaDeMuxerCore(); ~MediaDeMuxerCore(); // 提取视频 h264裸流 void de_muxer_video(std::string media_path,std::string out_video_path); // 提取音频 例如aac流 void de_muxer_audio(std::string media_path,std::string out_audio_path); // 使用容器封装的方式提取aac流 void de_muxer_audio_by_stream(std::string media_path,std::string out_audio_path); private: };

MediaDeMuxerCore.cpp #include "MediaDeMuxerCore.h" extern "C" { #include #include #include #include } MediaDeMuxerCore::MediaDeMuxerCore() { } AVFormatContext *avFormatContext = nullptr; AVPacket *avPacket = nullptr; AVFrame *avFrame = nullptr; FILE *h264_out = nullptr; FILE *audio_out = nullptr; AVBSFContext *bsf_ctx = nullptr; void init_h264_mp4toannexb(AVCodecParameters *avCodecParameters) { if (nullptr == bsf_ctx) { const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb"); // 2 初始化过滤器上下文 av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext; // 3 添加解码器属性 avcodec_parameters_copy(bsf_ctx->par_in, avCodecParameters); av_bsf_init(bsf_ctx); } } void MediaDeMuxerCore::de_muxer_video(std::string media_path, std::string out_video_path) { // 分配上下文 avFormatContext = avformat_alloc_context(); // 打开输入文件 avformat_open_input(&avFormatContext, media_path.c_str(), nullptr, nullptr); // 获取视频流索引 int video_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (video_index < 0) { std::cout size, h264_out); av_packet_unref(avPacket); //减少引用计数 } } else { av_packet_unref(avPacket); //减少引用计数 } } // 刷 fflush(h264_out); } avformat_close_input(&avFormatContext); } const int sampling_frequencies[] = { 96000, // 0x0 88200, // 0x1 64000, // 0x2 48000, // 0x3 44100, // 0x4 32000, // 0x5 24000, // 0x6 22050, // 0x7 16000, // 0x8 12000, // 0x9 11025, // 0xa 8000 // 0xb // 0xc d e f是保留的 }; int adts_header(char *const p_adts_header, const int data_length, const int profile, const int samplerate, const int channels) { int sampling_frequency_index = 3; // 默认使用48000hz int adtsLen = data_length + 7; // 匹配采样率 int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]); int i = 0; for (i = 0; i < frequencies_size; i++) { if (sampling_frequencies[i] == samplerate) { sampling_frequency_index = i; break; } } if (i >= frequencies_size) { std::cout codecpar->channels); // 先写adts头,有些是解封装出来就带有adts头的比如ts fwrite(adts_header_buf, 1, 7, audio_out); // 写入aac包 fwrite(avPacket->data, 1, avPacket->size, audio_out); av_packet_unref(avPacket); //减少引用计数 } else { av_packet_unref(avPacket); //减少引用计数 } } // 刷流 fflush(audio_out); } } void MediaDeMuxerCore::de_muxer_audio_by_stream(std::string media_path, std::string out_audio_path) { // 分配上下文 avFormatContext = avformat_alloc_context(); // 打开输入文件 avformat_open_input(&avFormatContext, media_path.c_str(), nullptr, nullptr); // 获取视频流索引 int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); audio_out = fopen(out_audio_path.c_str(), "wb"); if (audio_index < 0) { std::cout time_base) oformat = avOutputFormat; AVStream *aac_stream = avformat_new_stream(out_format_context, NULL); // 编码信息拷贝 int ret = avcodec_parameters_copy(aac_stream->codecpar,avFormatContext->streams[audio_index]->codecpar); ret = avio_open(&out_format_context->pb,out_audio_path.c_str(),AVIO_FLAG_WRITE); if(ret < 0){ std::cout time_base,aac_stream->time_base); ret = av_write_frame(out_format_context,avPacket); if(ret < 0){ std::cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3