【FFmpeg4.1.4】音视频分离器

音视频分离器

一、MP4提取H265裸流无效

FFmpeg解封装得到的AVPacket只包含了视频压缩数据,没有相关的参数集信息(比如:h265的vps头信息,h264的sps、pps头信息,AAC的adts头信息),不能初始化解码器。

二、添加头信息

StartCodePrefix的两种方式: Annex B和HVCC。

  1. Annex B:在NALU前加0x000001或者0x00000001 ;
  2. HVCC:在NALU前加上指示其长度的前缀。

MP4编码方式,Nalu开始四个字节表示数据长度。在FFmpeg中,这些头信息(VPS、SPS、PPS)是保存在解码器上下文(AVCodecContext)的extradata中的,所以我们需要为每一种格式的视频添加相应的解码头信息,这样解码器(MediaCodec)才能正确解析每一个AVPacket里的视频数据。FFmpeg提供了AVBitStreamFilter类实现头信息添加的功能。

三、音视频分离步骤

  1. 获取AVPacket
av_read_frame(fmt_ctx, &pkt)
  1. 添加头信息
if (av_bsf_send_packet(bsf_ctx, &pkt) < 0) {
	std::cerr << "failed to push" << std::endl;
	exit(1);
}			
while (av_bsf_receive_packet(bsf_ctx, &pkt) == 0);

源码

#include <iostream>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

#ifdef __cplusplus
}
#endif // __cplusplus

int demux(std::string mediafile) {
	const AVBitStreamFilter *filter = NULL;
	AVFormatContext *fmt_ctx = NULL;
	AVBSFContext *bsf_ctx;
	AVPacket pkt;
	AVCodecID video_codec_id;
	AVCodecID audio_codec_id;
	FILE *fp_vides = NULL, *fp_audes = NULL;
	int i, vid_idx, aud_idx;

	std::string videofile;
	std::string audiofile;

	if (avformat_open_input(&fmt_ctx, mediafile.c_str(), NULL, NULL) != 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not open input file");
		exit(1);
	}
	av_dump_format(fmt_ctx, 0, mediafile.c_str(), 0);

	if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
		avformat_free_context(fmt_ctx);
		av_log(NULL, AV_LOG_ERROR, "Failed to retrieve input stream information");
		exit(1);
	}
	
	for (i = 0; i<fmt_ctx->nb_streams; i++)
	{
		//vidoe
		if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			vid_idx = i;
			video_codec_id = fmt_ctx->streams[i]->codecpar->codec_id;
		}
		//audio
		else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
			audio_codec_id = fmt_ctx->streams[i]->codecpar->codec_id;
			aud_idx = i;
		}
		//such as subtitile
		else {
			//To DO
		}
	}
	
	switch (video_codec_id) {
	case AV_CODEC_ID_H264:
		filter = av_bsf_get_by_name("h264_mp4toannexb");
		videofile = "video_es.h264";
		break;
	case AV_CODEC_ID_HEVC:
		filter = av_bsf_get_by_name("hevc_mp4toannexb");
		videofile = "video_es.h265";
		break;
	default:
		av_log(NULL, AV_LOG_ERROR, "Unkonw Video AVCodecID");
		exit(1);
	}

	switch (audio_codec_id) {
	case AV_CODEC_ID_AAC:
		audiofile = "audio_es.aac";
		break;
	case AV_CODEC_ID_AC3:
		audiofile = "audio_es.aac";
		break;
	case AV_CODEC_ID_MP3:
		audiofile = "audio_es.mp3";
		break;
	default:
		av_log(NULL, AV_LOG_ERROR, "Unkonw Audio AVCodecID");
	}

	if (!filter)
	{
		av_log(NULL, AV_LOG_ERROR, "Unkonw bitstream filter");
		exit(1);
	}
	
	if (av_bsf_alloc(filter, &bsf_ctx) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Failed to alloc filter");
		exit(1);
	}

	//拷贝参数集信息
	avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[vid_idx]->codecpar);
	//初始化过滤器上下文
	av_bsf_init(bsf_ctx);

	fp_vides = fopen(videofile.c_str(), "wb");
	if (!fp_vides) {
		std::cerr << "Could not open file" << videofile.c_str() << std::endl;
		exit(1);
	}

	fp_audes = fopen(audiofile.c_str(), "wb");
	if (!fp_audes) {
		std::cerr << "Could not open file" << audiofile.c_str() << std::endl;
		exit(1);
	}

	while (av_read_frame(fmt_ctx, &pkt) >= 0)
	{
		if (pkt.stream_index == vid_idx) {
			if (av_bsf_send_packet(bsf_ctx, &pkt) < 0) {
				std::cerr << "failed to push" << std::endl;
				exit(1);
			}			
			while (av_bsf_receive_packet(bsf_ctx, &pkt) == 0);
			fwrite(pkt.data, pkt.size, 1, fp_vides);
		}			
		else if (pkt.stream_index == aud_idx)
			fwrite(pkt.data, pkt.size, 1, fp_audes);
		else
			;// such as subtitile
		av_packet_unref(&pkt);
	}
	fclose(fp_vides);
	fclose(fp_audes);
	avformat_close_input(&fmt_ctx);
	av_bsf_free(&bsf_ctx);
	return 0;
}

int main() {
	std::string mediafile = "bbb_sunflower_1080p_30fps_normal.mp4";
	demux(mediafile);
}

四、命令行

FFmpeg命令,从MP4中提取h265视频流

ffmpeg -i input.mp4 -codec copy -bsf: hevc_mp4toannexb -f hevc out.h265

-i input.mp4: 是输入的MP4文件
-codec copy: 从mp4中拷贝
-bsf: hevc_mp4toannexb: 从mp4拷贝到annexB封装
-f hevc: 采用hevc格式
out.h265: 输出的文件

上一篇:Qt 设置中文


下一篇:read方法读取ini文件报错'gbk' codec can't decode