音视频分离器
一、MP4提取H265裸流无效
FFmpeg解封装得到的AVPacket只包含了视频压缩数据,没有相关的参数集信息(比如:h265的vps头信息,h264的sps、pps头信息,AAC的adts头信息),不能初始化解码器。
二、添加头信息
StartCodePrefix的两种方式: Annex B和HVCC。
- Annex B:在NALU前加0x000001或者0x00000001 ;
- HVCC:在NALU前加上指示其长度的前缀。
MP4编码方式,Nalu开始四个字节表示数据长度。在FFmpeg中,这些头信息(VPS、SPS、PPS)是保存在解码器上下文(AVCodecContext)的extradata中的,所以我们需要为每一种格式的视频添加相应的解码头信息,这样解码器(MediaCodec)才能正确解析每一个AVPacket里的视频数据。FFmpeg提供了AVBitStreamFilter类实现头信息添加的功能。
三、音视频分离步骤
- 获取AVPacket
av_read_frame(fmt_ctx, &pkt)
- 添加头信息
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: 输出的文件