总体思路
从mp4文件中取出需要的video流,从流中取包,写入新的h264文件。
/* * 对于音频: * 音频每一帧都有帧首部,包含码率等信息。 * * 对于视频: * start code: 用于确定一帧的开始 * SPS/PPS: 确定分辨率,由于每帧的分辨率可能不同,所以每关键帧数据都应该有SPS和PPS * codec->extradata: 用于获取SPS/PPS * * 所以提取流数据时,除了多媒体数据本身,还应该拷贝帧头数据 * ffmpeg 为了方便编程,统一了api * * avformat_alloc_output_context2/ avformat_free_context * avformat_new_stream * avcodec_parameters_copy * * 文件首尾,帧首都可能添加信息,ffmpeg为了方便编程,统一了写输出文件的api * avformat_write_header * av_write_frame/ avinterleaved_write_frame * av_write_trailer * */ #include <stdio.h> #include <libavutil/log.h> #include <libavformat/avio.h> #include <libavformat/avformat.h> #define PrintError(errnum) do{ \ char errstr[512]; \ av_strerror(errnum, errstr, sizeof(errstr)); \ av_log(NULL, AV_LOG_ERROR, "failed to open %s : %s\n", in, errstr); \ }while(0) int main(int argc, char **argv) { AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL; AVOutputFormat *output_fmt = NULL; const char *in, *out; int stream_index, errnum; AVStream *o_stream = NULL, *i_stream = NULL; AVIOContext *pb = NULL; AVPacket pkt; // av_register_all(); av_log_set_level(AV_LOG_INFO); if (argc != 3) { av_log(NULL, AV_LOG_ERROR, "usage : a.out <in> <out>\n"); goto __failed__; } in = argv[1]; out = argv[2]; /********打开多媒体文件*******/ if ((errnum = avformat_open_input(&ifmt_ctx, in, NULL, NULL)) < 0) { PrintError(errnum); goto __failed__; } /*********找到需要的流***********/ if (0 > (stream_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO /*AVMEDIA_TYPE_VIDEO*/, -1, -1, NULL, 0))) { av_log(NULL, AV_LOG_ERROR, "find not best stream\n"); goto __failed__; } i_stream = ifmt_ctx->streams[stream_index]; /**********构造输出多媒体文件并设置默认参数**************/ #if 1 if (0 > (errnum = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out))) { PrintError(errnum); goto __failed__; } #else ofmt_ctx = avformat_alloc_context(); output_fmt = av_guess_format(NULL, out, NULL); if (!output_fmt) { av_log(NULL, AV_LOG_DEBUG, "根据目标生成输出容器失败!\n"); exit(1); } ofmt_ctx->oformat = output_fmt; #endif /*********在ofmt_ctx上构造流********/ if (!(o_stream = avformat_new_stream(ofmt_ctx, NULL))) { av_log(NULL, AV_LOG_ERROR, "failed to new stream\n"); goto __failed__; } /**********初始化输出流的参数*********************/ if (0 > avcodec_parameters_copy(o_stream->codecpar, i_stream->codecpar)) { av_log(NULL, AV_LOG_ERROR, "failed to copy stream codecpar\n"); goto __failed__; } o_stream->codecpar->codec_tag = 0; /***********以只写方式打开输出文件***********/ pb = ofmt_ctx->pb; if (0 > avio_open(&ofmt_ctx->pb, out, AVIO_FLAG_WRITE)) { av_log(NULL, AV_LOG_ERROR, "failed to open avio\n"); goto __failed__; } /***********初始化pkt通用域**********/ av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; /***********用默认值写头信息*************/ if (avformat_write_header(ofmt_ctx, NULL) < 0) { av_log(NULL, AV_LOG_DEBUG, "写入头部信息失败!\n"); exit(1); } /**********写数据*********/ while(av_read_frame(ifmt_ctx, &pkt) >= 0) { if (pkt.stream_index == stream_index) { // 需要保证包是需要的流 pkt.dts = av_rescale_q_rnd(pkt.dts, i_stream->time_base, o_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); // 四舍五入方式,重新度量显示时间戳 pkt.pts = av_rescale_q_rnd(pkt.pts, i_stream->time_base, o_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); // 重四舍五入方式,新度量解码时间戳 pkt.duration = av_rescale_q(pkt.duration, i_stream->time_base, o_stream->time_base) ; // 重新度量周期 pkt.pos = -1; // 将该包的已读取位置置-1 pkt.stream_index = 0; av_interleaved_write_frame(ofmt_ctx, &pkt); } av_packet_unref(&pkt); // 将pkt.buf 引用计数减一 } /*******写尾*******/ av_write_trailer(ofmt_ctx); av_log(NULL, AV_LOG_DEBUG, "44\n"); __failed__: if (ifmt_ctx) avformat_close_input(&ifmt_ctx); if (ofmt_ctx) avformat_free_context(ofmt_ctx); if (pb) avio_close(pb); return 0; }