FFmpeg 分离视频文件

时间:2025-03-08 20:55:01
  • #import ""
  • #import ""
  • #import ""
  • #import ""
  • // 1: Use H.264 Bitstream Filter
  • #define USE_H264BSF 0
  • @interface MediaFileSeparation(){
  • AVFormatContext *ifmtCtx;
  • AVFormatContext *ofmtCtxAudio;
  • AVFormatContext *ofmtCtxVideo;
  • //AVCodecContext *codecCtxAudio;
  • //AVCodecContext *codecCtxVideo;
  • AVPacket packet;
  • //AVBitStreamFilterContext *h264bsfc;
  • AVBSFContext *bsfCtx;
  • int audioIndex;
  • int videoIndex;
  • int ret;
  • }
  • @end
  • @implementation MediaFileSeparation
  • - (int)init:(const char *)inputFilePath videoFilePath:(char *)videoOutputFilePath audioFilePath:(char *) audioOutputFilePath{
  • ifmtCtx = NULL;
  • ofmtCtxAudio = NULL;
  • ofmtCtxVideo = NULL;
  • //h264bsfc = NULL;
  • bsfCtx = NULL;
  • audioIndex = -1;
  • videoIndex = -1;
  • ret = 0;
  • #if USE_H264BSF
  • // 声明
  • // 作用:分离某些封装格式的H264的时候,首先需要写入SPS和PPS,否则导致分离出来的数据没有SPS和PPS,而无法播放。
  • // h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
  • // 声明 h264_mp4toannexb hevc_mp4toannexb
  • const AVBitStreamFilter *pfilter = av_bsf_get_by_name("h264_mp4toannexb");
  • av_bsf_alloc(pfilter, &bsfCtx);
  • #endif
  • // 注册设备
  • //avdevice_register_all();
  • // 打开输入流
  • ret = avformat_open_input(&ifmtCtx, inputFilePath, 0, 0);
  • if(ret < 0){
  • fprintf(stderr, "avformat_open_input function failed. \n");
  • return ret;
  • }
  • // 获取输入流信息
  • ret = avformat_find_stream_info(ifmtCtx, 0);
  • if(ret < 0){
  • fprintf(stderr, "avformat_find_stream_info function failed. \n");
  • return ret;
  • }
  • #if 1
  • ret = [self openCodecParameters: &videoIndex withOutFilePath: &videoOutputFilePath withIfmtCtx: ifmtCtx withOfmtCtx: &ofmtCtxVideo withType: AVMEDIA_TYPE_VIDEO];
  • if(ret < 0){
  • fprintf(stderr, "openCodecContext function failed video can't copy parameters. \n");
  • return ret;
  • }
  • ret = [self openCodecParameters: &audioIndex withOutFilePath: &audioOutputFilePath withIfmtCtx: ifmtCtx withOfmtCtx: &ofmtCtxAudio withType: AVMEDIA_TYPE_AUDIO];
  • if(ret < 0){
  • fprintf(stderr, "openCodecContext function failed audio can't copy parameters. \n");
  • return ret;
  • }
  • #endif
  • #if 0
  • ret = [self openCodecContext:ifmtCtx];
  • if(ret < 0){
  • fprintf(stderr, "openCodecContext function failed. \n");
  • return ret;
  • }
  • #endif
  • // 打开输出文件: 视频
  • if(!(ofmtCtxVideo->oformat->flags & AVFMT_NOFILE)){
  • ret = avio_open(&ofmtCtxVideo->pb, videoOutputFilePath, AVIO_FLAG_WRITE);
  • if(ret < 0){
  • fprintf(stderr, "avio_open function failed can't open output file: %s. \n", videoOutputFilePath);
  • return ret;
  • }
  • }
  • // 打开输出文件: 音频
  • if(!(ofmtCtxAudio->oformat->flags & AVFMT_NOFILE)){
  • ret = avio_open(&ofmtCtxAudio->pb, audioOutputFilePath, AVIO_FLAG_WRITE);
  • if(ret < 0){
  • fprintf(stderr, "avio_open function failed can't open output file: %s. \n", audioOutputFilePath);
  • return ret;
  • }
  • }
  • // 写头文件: 视频
  • ret = avformat_write_header(ofmtCtxVideo, NULL);
  • if(ret < 0){
  • fprintf(stderr, "avformat_write_header function failed error opening video write header");
  • return ret;
  • }
  • // 写头文集: 音频
  • ret = avformat_write_header(ofmtCtxAudio, NULL);
  • if(ret < 0){
  • fprintf(stderr, "avformat_write_header function failed error opening audio write header");
  • return ret;
  • }
  • // DumpFormat 转存储格式
  • // av_dump_format: 是一个手工调试函数,能让我们看到 ifmtCtx->streams 里面的内容
  • printf("\nInput Video: \n");
  • av_dump_format(ifmtCtx, 0, inputFilePath, 0);
  • printf("\nOutput Video: \n");
  • av_dump_format(ofmtCtxVideo, 0, videoOutputFilePath, 1);
  • printf("\nOutput Audio: \n");
  • av_dump_format(ofmtCtxAudio, 0, audioOutputFilePath, 1);
  • printf("\n");
  • printf("MediaFileSeparation init success. \n");
  • return ret;
  • }
  • /// 拆分转换 Mp4 数据
  • - (void)convertFrames{
  • while (true) {
  • AVFormatContext *ofmtCtx;
  • AVStream *inStream, *outStream;
  • //读取输入帧数据
  • int ret = av_read_frame(ifmtCtx, &packet);
  • if(ret < 0){
  • break;
  • }
  • //获取输入流
  • inStream = ifmtCtx->streams[packet.stream_index];
  • //判断是否为视频流下标
  • if(packet.stream_index == videoIndex){
  • outStream = ofmtCtxVideo->streams[0];
  • ofmtCtx = ofmtCtxVideo;
  • #if USE_H264BSF
  • // 使用
  • // 每个AVPacket的数据有如下变化:
  • // 每个Packet的data增加了h264的NALU起始码{0,0,0,1}
  • // 每个IDR帧数据前面增加了SPS和PPS.
  • //av_bitstream_filter_filter(h264bsfc, inStream->codec, NULL, &, &, , , 0);
  • av_bsf_send_packet(bsfCtx, &packet);
  • av_bsf_receive_packet(bsfCtx, &packet);
  • #endif
  • }else if(packet.stream_index == audioIndex){
  • //判断是否为音频流下标
  • outStream = ofmtCtxAudio->streams[0];
  • ofmtCtx = ofmtCtxAudio;
  • }else{
  • continue;
  • }
  • // Convert PTS/DTS 转换
  • = av_rescale_q_rnd(, inStream->time_base, outStream->time_base, (enum AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
  • = av_rescale_q_rnd(, inStream->time_base, outStream->time_base, (enum AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
  • = av_rescale_q(, inStream->time_base, outStream->time_base);
  • // 写数据
  • = -1;
  • packet.stream_index = 0;
  • ret = av_interleaved_write_frame(ofmtCtx, &packet);
  • if(ret < 0){
  • fprintf(stderr, "av_interleaved_write_frame function failed muxing packet. \n");
  • continue;
  • }
  • // 释放数据
  • av_packet_unref(&packet);
  • }
  • }
  • /// 从输入格式中获取参数赋值给输出格式参数
  • /// - Parameters:
  • /// - streanIndex: 流下标
  • /// - ifmtCtx: 输入格式
  • /// - ofmtCtx: 输出格式
  • /// - type: 类型
  • - (int)openCodecParameters:(int *)streamIndex withOutFilePath:(char **)outFilePath withIfmtCtx:(AVFormatContext *)ifmtCtx withOfmtCtx:(AVFormatContext **)ofmtCtx withType:(enum AVMediaType)type{
  • // 输入输出流
  • AVStream *outStream = NULL, *inStream = NULL;
  • // 获取流索引
  • int index = av_find_best_stream(ifmtCtx, type, -1, -1, NULL, 0);
  • if(index < 0){
  • fprintf(stderr, "av_find_best_stream can't find %s stream in input file. \n", av_get_media_type_string(type));
  • return -1;
  • }
  • // 获取到指定的输入流
  • inStream = ifmtCtx->streams[index];
  • // 获取文件格式(后缀名) hevc/h264 aac
  • const char *formatName = avcodec_get_name(inStream->codecpar->codec_id);
  • // 拼接文件格式,回传
  • // *outFilePath = [*outFilePath stringByAppendingPathExtension:[[NSString alloc] initWithUTF8String:formatName]];
  • strcat(*outFilePath, ".");
  • strcat(*outFilePath, formatName);
  • // 创建输出上下文 &ofmtCtx
  • int ret = avformat_alloc_output_context2(ofmtCtx, NULL, NULL, *outFilePath);
  • if(ret < 0){
  • fprintf(stderr, "avformat_alloc_output_context2 function failed. \n");
  • }
  • // 创建指定的输出流 ofmtCtx
  • outStream = avformat_new_stream(*ofmtCtx, NULL);
  • if(!outStream){
  • fprintf(stderr , "avformat_new_stream function allocate output stream failed. \n ");
  • return -1;
  • }
  • // 输入参数拷贝到输出参数中
  • ret = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);
  • if(ret < 0){
  • fprintf(stderr, "avcodec_parameters_copy function copy codec parameters failed. \n");
  • return ret;
  • }
  • // outStream->codec->codec_tag
  • outStream->codecpar->codec_tag = 0;
  • // ofmtCtx
  • if((*ofmtCtx)->oformat->flags & AVFMT_GLOBALHEADER){
  • // outStream->codec->flags
  • outStream->parser->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  • }
  • // 流下标回传
  • *streamIndex = index;
  • return ret;
  • }
  • /// 根据输入流中的 Codec 拷贝到 输出流中
  • /// - Parameter ifmtCtx: 输入流格式
  • - (int)openCodecContext: (AVFormatContext *)ifmtCtx{
  • int ret = -1;
  • for (int i = 0; i < ifmtCtx->nb_streams; i++) {
  • AVFormatContext *ofmtCtx;
  • AVStream *inStream = ifmtCtx->streams[i];
  • AVStream *outStream = NULL;
  • //inStream->codec->codec_type
  • if(inStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
  • videoIndex = i;
  • //AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
  • //avcodec_parameters_to_context(pCodecCtx, inStream->codecpar);
  • //outStream = avformat_new_stream(ofmtCtxVideo, pCodecCtx->codec);
  • outStream = avformat_new_stream(ofmtCtxVideo, inStream->codec->codec);
  • ofmtCtx = ofmtCtxVideo;
  • }else if(inStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
  • audioIndex = i;
  • outStream = avformat_new_stream(ofmtCtxAudio, inStream->codec->codec);
  • ofmtCtx = ofmtCtxAudio;
  • }else{
  • break;
  • }
  • // 输出流为空
  • if(!outStream){
  • fprintf(stderr, "avformat_new_stream function failed to allocate output stream. \n");
  • return ret;
  • }
  • if(avcodec_copy_context(outStream->codec, inStream->codec) < 0){
  • fprintf(stderr, "avcodec_copy_context function failed to copy context form input to output stream codec context. \n");
  • return ret;
  • }
  • // outStream->codec->codec_tag
  • outStream->codecpar->codec_tag = 0;
  • if(ofmtCtx->oformat->flags & AVFMT_GLOBALHEADER){
  • //outStream->codec->flags
  • outStream->parser->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  • }
  • }
  • return 0;
  • }
  • /// 停止
  • - (void)stop{
  • #if USE_H264BSF
  • // 释放关闭
  • //av_bitstream_filter_close(h264bsfc);
  • av_bsf_free(&bsfCtx);
  • #endif
  • // 写入文件结束标识 write file trailer
  • av_write_trailer(ofmtCtxVideo);
  • av_write_trailer(ofmtCtxAudio);
  • }
  • /// 关闭
  • - (void)close{
  • // 关闭输入流
  • if(ifmtCtx){
  • avformat_close_input(&ifmtCtx);
  • ifmtCtx = NULL;
  • }
  • // 关闭视频文件
  • if(ofmtCtxVideo && !(ofmtCtxVideo->oformat->flags & AVFMT_NOFILE)){
  • avio_close(ofmtCtxVideo->pb);
  • }
  • // 关闭音频文件
  • if(ofmtCtxAudio && !(ofmtCtxAudio->oformat->flags & AVFMT_NOFILE)){
  • avio_close(ofmtCtxAudio->pb);
  • }
  • // 释放视频内容
  • if(ofmtCtxVideo){
  • avformat_free_context(ofmtCtxVideo);
  • ofmtCtxVideo = NULL;
  • }
  • // 释放音频内容
  • if(ofmtCtxAudio){
  • avformat_free_context(ofmtCtxAudio);
  • ofmtCtxAudio = NULL;
  • }
  • printf("MediaFileSeparation close");
  • }
  • /// 销毁
  • - (void)dealloc{
  • [self close];
  • }
  • @end