zoukankan      html  css  js  c++  java
  • 最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)

    =====================================================

    最简单的基于FFmpeg的封装格式处理系列文章列表:

    最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)

    最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)

    最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)

    最简单的基于FFMPEG的封装格式处理:封装格式转换(remuxer)

    =====================================================


    简介

    打算记录一下基于FFmpeg的封装格式处理方面的样例。包括了视音频分离,复用,封装格式转换。

    这是第2篇。

    本文记录一个基于FFmpeg的视音频分离器(Simplest FFmpeg demuxer)。视音频分离器(Demuxer)即是将封装格式数据(比如MKV)中的视频压缩数据(比如H.264)和音频压缩数据(比如AAC)分离开。如图所看到的。在这个过程中并不涉及到编码和解码。


    本文记录的程序能够将一个MPEG2TS封装的视频文件(当中视频编码为H.264,音频编码为AAC)分离成为两个文件:一个H.264编码的视频码流文件,一个AAC编码的音频码流文件。
    前一篇文章中,记录一个简单版的视音频分离器。相比于前一篇文中的分离器,本篇文章记录的分离器复杂了非常多。相比于简单版的分离器,学习的难度大了一些。

    可是该分离器能够非常好地处理FFmpeg支持的各种格式(比如分离AAC音频流),拥有更好的有用性。

    流程图


    程序的流程例如以下图所看到的。

    从流程图中能够看出,一共初始化了3个AVFormatContext。当中1个用于输入,另外2个分别用于视频输出和音频输出。

    3个AVFormatContext初始化之后,通过avcodec_copy_context()函数能够将输入视频/音频的參数拷贝至输出视频/音频的AVCodecContext结构体。

    最后,通过av_read_frame()获取AVPacket,依据AVPacket类型的不同,分别使用av_interleaved_write_frame()写入不同的输出文件里就可以。


     
    PS:对于某些封装格式(比如MP4/FLV/MKV等)中的H.264,须要用到名称为“h264_mp4toannexb”的bitstream filter。这一点在前一篇文章《最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)》中,已经有过具体叙述,这里不再反复。



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

    avformat_open_input():打开输入文件。
    avcodec_copy_context():赋值AVCodecContext的參数。
    avformat_alloc_output_context2():初始化输出文件。
    avio_open():打开输出文件。
    avformat_write_header():写入文件头。


    av_read_frame():从输入文件读取一个AVPacket。
    av_interleaved_write_frame():写入一个AVPacket到输出文件。
    av_write_trailer():写入文件尾。


    代码

    下面贴上代码:

    /**
     * 最简单的基于FFmpeg的视音频分离器
     * Simplest FFmpeg Demuxer
     *
     * 雷霄骅 Lei Xiaohua
     * leixiaohua1020@126.com
     * 中国传媒大学/数字电视技术
     * Communication University of China / Digital TV Technology
     * http://blog.csdn.net/leixiaohua1020
     *
     * 本程序能够将封装格式中的视频码流数据和音频码流数据分离出来。
     * 在该样例中。 将MPEG2TS的文件分离得到H.264视频码流文件和AAC
     * 音频码流文件。
     *
     * This software split a media file (in Container such as 
     * MKV, FLV, AVI...) to video and audio bitstream.
     * In this example, it demux a MPEG2TS file to H.264 bitstream
     * and AAC bitstream.
     */
    
    #include <stdio.h>
    
    #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
    
    /*
    FIX: H.264 in some container format (FLV, MP4, MKV etc.) need 
    "h264_mp4toannexb" bitstream filter (BSF)
      *Add SPS,PPS in front of IDR frame
      *Add start code ("0,0,0,1") in front of NALU
    H.264 in some container (MPEG2TS) don't need this BSF.
    */
    //'1': Use H.264 Bitstream Filter 
    #define USE_H264BSF 0
    
    int main(int argc, char* argv[])
    {
    	AVOutputFormat *ofmt_a = NULL,*ofmt_v = NULL;
    	//(Input AVFormatContext and Output AVFormatContext)
    	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx_a = NULL, *ofmt_ctx_v = NULL;
    	AVPacket pkt;
    	int ret, i;
    	int videoindex=-1,audioindex=-1;
    	int frame_index=0;
    
    	const char *in_filename  = "cuc_ieschool.ts";//Input file URL
    	//char *in_filename  = "cuc_ieschool.mkv";
    	const char *out_filename_v = "cuc_ieschool.h264";//Output file URL
    	//char *out_filename_a = "cuc_ieschool.mp3";
    	const char *out_filename_a = "cuc_ieschool.aac";
    
    	av_register_all();
    	//Input
    	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
    		printf( "Could not open input file.");
    		goto end;
    	}
    	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
    		printf( "Failed to retrieve input stream information");
    		goto end;
    	}
    
    	//Output
    	avformat_alloc_output_context2(&ofmt_ctx_v, NULL, NULL, out_filename_v);
    	if (!ofmt_ctx_v) {
    		printf( "Could not create output context
    ");
    		ret = AVERROR_UNKNOWN;
    		goto end;
    	}
    	ofmt_v = ofmt_ctx_v->oformat;
    
    	avformat_alloc_output_context2(&ofmt_ctx_a, NULL, NULL, out_filename_a);
    	if (!ofmt_ctx_a) {
    		printf( "Could not create output context
    ");
    		ret = AVERROR_UNKNOWN;
    		goto end;
    	}
    	ofmt_a = ofmt_ctx_a->oformat;
    
    	for (i = 0; i < ifmt_ctx->nb_streams; i++) {
    			//Create output AVStream according to input AVStream
    			AVFormatContext *ofmt_ctx;
    			AVStream *in_stream = ifmt_ctx->streams[i];
    			AVStream *out_stream = NULL;
    			
    			if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
    				videoindex=i;
    				out_stream=avformat_new_stream(ofmt_ctx_v, in_stream->codec->codec);
    				ofmt_ctx=ofmt_ctx_v;
    			}else if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
    				audioindex=i;
    				out_stream=avformat_new_stream(ofmt_ctx_a, in_stream->codec->codec);
    				ofmt_ctx=ofmt_ctx_a;
    			}else{
    				break;
    			}
    			
    			if (!out_stream) {
    				printf( "Failed allocating output stream
    ");
    				ret = AVERROR_UNKNOWN;
    				goto end;
    			}
    			//Copy the settings of AVCodecContext
    			if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
    				printf( "Failed to copy context from input to output stream codec context
    ");
    				goto end;
    			}
    			out_stream->codec->codec_tag = 0;
    
    			if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
    				out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    	}
    
    	//Dump Format------------------
    	printf("
    ==============Input Video=============
    ");
    	av_dump_format(ifmt_ctx, 0, in_filename, 0);
    	printf("
    ==============Output Video============
    ");
    	av_dump_format(ofmt_ctx_v, 0, out_filename_v, 1);
    	printf("
    ==============Output Audio============
    ");
    	av_dump_format(ofmt_ctx_a, 0, out_filename_a, 1);
    	printf("
    ======================================
    ");
    	//Open output file
    	if (!(ofmt_v->flags & AVFMT_NOFILE)) {
    		if (avio_open(&ofmt_ctx_v->pb, out_filename_v, AVIO_FLAG_WRITE) < 0) {
    			printf( "Could not open output file '%s'", out_filename_v);
    			goto end;
    		}
    	}
    
    	if (!(ofmt_a->flags & AVFMT_NOFILE)) {
    		if (avio_open(&ofmt_ctx_a->pb, out_filename_a, AVIO_FLAG_WRITE) < 0) {
    			printf( "Could not open output file '%s'", out_filename_a);
    			goto end;
    		}
    	}
    
    	//Write file header
    	if (avformat_write_header(ofmt_ctx_v, NULL) < 0) {
    		printf( "Error occurred when opening video output file
    ");
    		goto end;
    	}
    	if (avformat_write_header(ofmt_ctx_a, NULL) < 0) {
    		printf( "Error occurred when opening audio output file
    ");
    		goto end;
    	}
    	
    #if USE_H264BSF
    	AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb"); 
    #endif
    
    	while (1) {
    		AVFormatContext *ofmt_ctx;
    		AVStream *in_stream, *out_stream;
    		//Get an AVPacket
    		if (av_read_frame(ifmt_ctx, &pkt) < 0)
    			break;
    		in_stream  = ifmt_ctx->streams[pkt.stream_index];
    
    		
    		if(pkt.stream_index==videoindex){
    			out_stream = ofmt_ctx_v->streams[0];
    			ofmt_ctx=ofmt_ctx_v;
    			printf("Write Video Packet. size:%d	pts:%lld
    ",pkt.size,pkt.pts);
    #if USE_H264BSF
    			av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
    #endif
    		}else if(pkt.stream_index==audioindex){
    			out_stream = ofmt_ctx_a->streams[0];
    			ofmt_ctx=ofmt_ctx_a;
    			printf("Write Audio Packet. size:%d	pts:%lld
    ",pkt.size,pkt.pts);
    		}else{
    			continue;
    		}
    
    
    		//Convert PTS/DTS
    		pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    		pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
    		pkt.pos = -1;
    		pkt.stream_index=0;
    		//Write
    		if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {
    			printf( "Error muxing packet
    ");
    			break;
    		}
    		//printf("Write %8d frames to output file
    ",frame_index);
    		av_free_packet(&pkt);
    		frame_index++;
    	}
    
    #if USE_H264BSF
    	av_bitstream_filter_close(h264bsfc);  
    #endif
    
    	//Write file trailer
    	av_write_trailer(ofmt_ctx_a);
    	av_write_trailer(ofmt_ctx_v);
    end:
    	avformat_close_input(&ifmt_ctx);
    	/* close output */
    	if (ofmt_ctx_a && !(ofmt_a->flags & AVFMT_NOFILE))
    		avio_close(ofmt_ctx_a->pb);
    
    	if (ofmt_ctx_v && !(ofmt_v->flags & AVFMT_NOFILE))
    		avio_close(ofmt_ctx_v->pb);
    
    	avformat_free_context(ofmt_ctx_a);
    	avformat_free_context(ofmt_ctx_v);
    
    
    	if (ret < 0 && ret != AVERROR_EOF) {
    		printf( "Error occurred.
    ");
    		return -1;
    	}
    	return 0;
    }
    
    


    结果

    输入文件为:
    cuc_ieschool.ts:MPEG2TS封装格式数据。


    输出文件为:
    cuc_ieschool.h264:H.264视频码流数据。
    cuc_ieschool.aac:AAC音频码流数据。

    下载


    simplest ffmpeg format


    项目主页

    SourceForge:https://sourceforge.net/projects/simplestffmpegformat/

    Github:https://github.com/leixiaohua1020/simplest_ffmpeg_format

    开源中国:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_format


    CSDN下载地址:
    http://download.csdn.net/detail/leixiaohua1020/8005317

    工程中包括4个样例:

    simplest_ffmpeg_demuxer_simple:视音频分离器(简化版)。

    simplest_ffmpeg_demuxer:视音频分离器。

    simplest_ffmpeg_muxer:视音频复用器。

    simplest_ffmpeg_remuxer:封装格式转换器。


    更新-1.1==================================================

    修复了下面问题:
    (1)Release版本号下的执行问题
    (2)simplest_ffmpeg_muxer分装H.264裸流的时候丢失声音的错误

    CSDN下载地址:

    http://download.csdn.net/detail/leixiaohua1020/8284309


    更新-1.2 (2015.2.13)=========================================

    这次考虑到了跨平台的要求。调整了源码。经过这次调整之后,源码能够在下面平台编译通过:

    VC++:打开sln文件就可以编译。无需配置。

    cl.exe:打开compile_cl.bat就可以命令行下使用cl.exe进行编译,注意可能须要依照VC的安装路径调整脚本里面的參数。编译命令例如以下。

    ::VS2010 Environment
    call "D:Program FilesMicrosoft Visual Studio 10.0VCvcvarsall.bat"
    ::include
    @set INCLUDE=include;%INCLUDE%
    ::lib
    @set LIB=lib;%LIB%
    ::compile and link
    cl simplest_ffmpeg_demuxer.cpp /link avcodec.lib avformat.lib avutil.lib ^
    avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF

    MinGW:MinGW命令行下执行compile_mingw.sh就可以使用MinGW的g++进行编译。编译命令例如以下。

    g++ simplest_ffmpeg_demuxer.cpp -g -o simplest_ffmpeg_demuxer.exe 
    -I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil

    GCC:Linux或者MacOS命令行下执行compile_gcc.sh就可以使用GCC进行编译。编译命令例如以下。

    gcc simplest_ffmpeg_demuxer.cpp -g -o simplest_ffmpeg_demuxer.out 
    -I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil
    PS:相关的编译命令已经保存到了工程目录中

    CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8445303

    SourceForge上已经更新。


  • 相关阅读:
    老板说,你给我1分钟内下载100张图片!So,easy!
    测试用例 setup 和 和 teardown
    pytest环境准备与入门
    测试工程需要明白的Monkey测试
    5.通过定位实现二级菜单
    4.CSS中float导致的高度坍塌问题及解决方法
    3.使用float实现文字环绕图片
    2.reset.css文件
    1.图片元素<img>和<map>的联用
    1.引用js文件中的函数调用
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/6999667.html
Copyright © 2011-2022 走看看