總體思路
從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; }