最简单的基于 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()。示例代码如下:

cpp 复制代码
	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 的过程。

cpp 复制代码
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 个字节改为起始码。示例代码如下所示:

cpp 复制代码
char nal_start[]={0,0,0,1};
memcpy(packet->data,nal_start,4);

经过上述两步也可以得到可以播放的 H.264 码流,相对于第一种方法来说复杂一些。

当封装格式为 MPEG2TS 的时候,不存在上述问题。

程序的流程如下图所示:

从流程图中可以看出,将每个通过 av_read_frame() 获得的 AVPacket 中的数据直接写入文件即可。

简单介绍一下流程中各个重要函数的意义:

  1. avformat_open_input():打开输入文件。
  2. av_read_frame():获取一个 AVPacket。
  3. fwrite():根据得到的 AVPacket 的类型不同,分别写入到不同的文件中。

源程序:

cpp 复制代码
// 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

参考链接

  1. 使用FFMPEG类库分离出多媒体文件中的音频码流
  2. 使用FFMPEG类库分离出多媒体文件中的H.264码流
相关推荐
Mr.Z.41121 分钟前
【历年CSP-S复赛第一题】暴力解法与正解合集(2019-2022)
c++
Death20025 分钟前
使用Qt进行TCP和UDP网络编程
网络·c++·qt·tcp/ip
郭二哈36 分钟前
C++——list
开发语言·c++·list
yunhuibin1 小时前
ffmpeg面向对象——拉流协议匹配机制探索
学习·ffmpeg
黑不溜秋的1 小时前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光1 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
￴ㅤ￴￴ㅤ9527超级帅2 小时前
LeetCode hot100---二叉树专题(C++语言)
c++·算法·leetcode
_GR2 小时前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
Death2002 小时前
Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件
c语言·开发语言·c++·qt·c#
六点半8882 小时前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法