最简单的基于 FFmpeg 的视音频分离器 - 简化版
- 最简单的基于 FFmpeg 的视音频分离器 - 简化版
- 正文
- 结果
- 工程文件下载
- 参考链接
最简单的基于 FFmpeg 的视音频分离器 - 简化版
参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)
正文
本文介绍一个视音频分离器(Demuxer)。
视音频分离器即是将封装格式数据(例如 MKV)中的视频压缩数据(例如 H.264)和音频压缩数据(例如 AAC)分离开,如下图所示。
在这个过程中并不涉及到编码和解码。
本文记录的程序将一个 FLV 封装的文件(其中视频编码为 H.264,音频编码为 MP3)分离成为两个文件:一个 H.264 编码的视频码流文件,一个 MP3 编码的音频码流文件。
需要注意的是,本文介绍的是一个简单版的视音频分离器(Demuxer)。该分离器的优点是代码十分简单,很好理解。但是缺点是并不适用于一些格式。对于 MP3 编码的音频是没有问题的。但是在分离 MP4/FLV/MKV 等一些格式中的 AAC 编码的码流的时候,得到的 AAC 码流是不能播放的。原因是存储 AAC 数据的 AVPacket 的 data 字段中的数据是不包含 7 字节 ADTS 文件头的“砍头”的数据,是无法直接解码播放的(当然如果在这些数据前面手工加上 7 字节的 ADTS 文件头的话,就可以播放了)。
分离某些封装格式(例如 MP4/FLV/MKV 等)中的 H.264 的时候,需要首先写入 SPS 和 PPS,否则会导致分离出来的数据没有 SPS、PPS 而无法播放。H.264 码流的 SPS 和 PPS 信息存储在 AVCodecContext 结构体的 extradata 中。需要使用 FFmpeg 中名称为“h264_mp4toannexb”的 bitstream filter 处理。有两种处理方式:
(1)使用 bitstream filter 处理每个 AVPacket(简单)
把每个 AVPacket 中的数据(data 字段)经过 bitstream filter “过滤”一遍。关键函数是 av_bitstream_filter_filter()。示例代码如下:
AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
while(av_read_frame(ifmt_ctx, &pkt)>=0){
if(pkt.stream_index==videoindex){
av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
fwrite(pkt.data,1,pkt.size,fp_video);
//...
}
av_free_packet(&pkt);
}
av_bitstream_filter_close(h264bsfc);
上述代码中,把 av_bitstream_filter_filter() 的输入数据和输出数据(分别对应第 4,5,6,7 个参数)都设置成 AVPacket 的 data 字段就可以了。
需要注意的是 bitstream filter 需要初始化和销毁,分别通过函数 av_bitstream_filter_init() 和 av_bitstream_filter_close()。
经过上述代码处理之后,AVPacket 中的数据有如下变化:
-
每个 AVPacket 的data 添加了 H.264 的 NALU 的起始码 {0,0,0,1}。
-
每个 IDR 帧数据前面添加了 SPS 和 PPS。
(2)手工添加 SPS,PPS(稍微复杂)
将 AVCodecContext 的 extradata 数据经过 bitstream filter 处理之后得到 SPS、PPS,拷贝至每个 IDR 帧之前。下面代码示例了写入 SPS、PPS 的过程。
FILE *fp=fopen("test.264","ab");
AVCodecContext *pCodecCtx=...
unsigned char *dummy=NULL;
int dummy_len;
AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("h264_mp4toannexb");
av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, 0, 0);
fwrite(pCodecCtx->extradata,pCodecCtx->extradata_size,1,fp);
av_bitstream_filter_close(bsfc);
free(dummy);
然后修改 AVPacket 的 data。把前 4 个字节改为起始码。示例代码如下所示:
char nal_start[]={0,0,0,1};
memcpy(packet->data,nal_start,4);
经过上述两步也可以得到可以播放的 H.264 码流,相对于第一种方法来说复杂一些。
当封装格式为 MPEG2TS 的时候,不存在上述问题。
程序的流程如下图所示:
从流程图中可以看出,将每个通过 av_read_frame() 获得的 AVPacket 中的数据直接写入文件即可。
简单介绍一下流程中各个重要函数的意义:
- avformat_open_input():打开输入文件。
- av_read_frame():获取一个 AVPacket。
- fwrite():根据得到的 AVPacket 的类型不同,分别写入到不同的文件中。
源程序:
// Simplest FFmpeg Demuxer Simple.cpp : 定义控制台应用程序的入口点。
//
/**
* 最简单的基于 FFmpeg 的视音频分离器(简化版)
* Simplest FFmpeg Demuxer Simple
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序可以将封装格式中的视频码流数据和音频码流数据分离出来。
* 在该例子中, 将FLV的文件分离得到 H.264 视频码流文件和 MP3 音频码流文件。
*
* 注意:
* 这个是简化版的视音频分离器。
* 与原版的不同在于,没有初始化输出视频流和音频流的 AVFormatContext,
* 而是直接将解码后的得到的 AVPacket 中的的数据通过 fwrite() 写入文件。
* 这样做的好处是流程比较简单。
* 坏处是对一些格式的视音频码流是不适用的,比如说 FLV/MP4/MKV 等格式中的 AAC 码流
* (上述封装格式中的 AAC 的 AVPacket 中的数据缺失了 7 字节的 ADTS 文件头)。
*
* This software split a media file (in Container such as MKV, FLV, AVI...)
* to video and audio bitstream.
* In this example, it demux a FLV file to H.264 bitstream and MP3 bitstream.
*
* Note:
* This is a simple version of "Simplest FFmpeg Demuxer".
* It is more simple because it doesn't init Output Video/Audio stream's AVFormatContext.
* It writes AVPacket's data to files directly.
* The advantages of this method is simple.
* The disadvantages of this method is it's not suitable for some kind of bitstreams.
* Forexample, AAC bitstream in FLV/MP4/MKV Container Format
* (data in AVPacket lack of 7 bytes of ADTS header).
*
*/
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)
// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
// Windows
extern "C"
{
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
// 1: Use H.264 Bitstream Filter
#define USE_H264BSF 1
int main(int argc, char* argv[])
{
AVFormatContext *ifmt_ctx = NULL;
AVPacket pkt;
int ret;
int videoindex = -1, audioindex = -1;
// Input file URL
const char *in_filename = "cuc_ieschool.flv";
// Output video file URL
const char *out_video_filename = "cuc_ieschool.h264";
// Output audio file URL
const char *out_audio_filename = "cuc_ieschool.mp3";
av_register_all();
// 输入
ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
if (ret < 0)
{
printf("Could not open input file.\n");
return -1;
}
ret = avformat_find_stream_info(ifmt_ctx, 0);
if (ret < 0)
{
printf("Failed to retrieve input stream information.\n");
return -1;
}
// Print some input information
av_dump_format(ifmt_ctx, 0, in_filename, 0);
for (size_t i = 0; i < ifmt_ctx->nb_streams; i++)
{
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
videoindex = i;
else if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
audioindex = i;
}
FILE *fp_audio = fopen(out_audio_filename, "wb+");
FILE *fp_video = fopen(out_video_filename, "wb+");
/*
FIX: H.264 in some container formats (FLV, MP4, MKV etc.)
need "h264_mp4toannexb" bitstream filter (BSF).
1. Add SPS,PPS in front of IDR frame
2. Add start code ("0,0,0,1") in front of NALU
H.264 in some containers (such as MPEG2TS) doesn't need this BSF.
*/
#if USE_H264BSF
AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif
while (1)
{
// 获取一个 AVPacket
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
{
break;
}
if (pkt.stream_index == videoindex)
{
#if USE_H264BSF
av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
printf("Write a video packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
fwrite(pkt.data, 1, pkt.size, fp_video);
}
else if (pkt.stream_index == audioindex)
{
/*
AAC in some container formats (FLV, MP4, MKV etc.) need to
add 7 Bytes ADTS Header in front of AVPacket data manually.
Other Audio Codec (MP3...) works well.
*/
printf("Write a audio packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
fwrite(pkt.data, 1, pkt.size, fp_audio);
}
av_free_packet(&pkt);
}
#if USE_H264BSF
av_bitstream_filter_close(h264bsfc);
#endif
fclose(fp_video);
fclose(fp_audio);
avformat_close_input(&ifmt_ctx);
system("pause");
return 0;
}
结果
运行程序,输出如下:
输入文件为:
cuc_ieschool.flv:FLV 封装格式数据。
输出文件为:
cuc_ieschool.mp3:MP3 音频码流数据。
cuc_ieschool.h264:H.264 视频码流数据。
工程文件下载
GitHub:UestcXiye / Simplest-FFmpeg-Demuxer-Simple
CSDN:Simplest FFmpeg Demuxer Simple.zip
参考链接
- 使用FFMPEG类库分离出多媒体文件中的音频码流
- 使用FFMPEG类库分离出多媒体文件中的H.264码流