文章目錄
- 前言
- 一、封裝格式簡介
- 1、FFmpeg 中的封裝格式
- 2、查看 FFmpeg 支持的封裝格式
- 二、API 介紹
- 三、 實戰 1:解封裝
- 1、原理講解
- 2、示例源碼 1
- 3、運行結果 1
- 4、示例源碼 2
- 5、運行結果 2
- 四、 實戰 2:轉封裝
- 1、原理講解
- 2、示例源碼
- 3、運行結果
前言
AVFormatContext 是一個貫穿始終的數據結構,很多函數都用到它作為參數,是輸入輸出相關信息的一個容器,本文講解 AVFormatContext 的封裝層,主要包括兩大數據結構:AVInputFormat,AVOutputFormat。
一、封裝格式簡介
封裝格式(container format)可以看作是編碼流(音頻流、視頻流等)數據的一層外殼,將編碼后的數據存儲于此封裝格式的文件之內。
封裝又稱容器,容器的稱法更為形象,所謂容器,就是存放內容的器具,例如飲料是內容,那么裝飲料的瓶子就是容器。
不同封裝格式適用于不同的場合,支持的編碼格式不一樣,幾個常用的封裝格式如下:
1、FFmpeg 中的封裝格式
FFmpeg 關于封裝格式的處理涉及打開輸入文件、打開輸出文件、從輸入文件讀取編碼幀、往輸出文件寫入編碼幀這幾個步驟,這些都不涉及編碼解碼層面。
在 FFmpeg 中,mux 指復用,是 multiplex 的縮寫,表示將多路流(視頻、音頻、字幕等)混入一路輸出中(普通文件、流等)。demux 指解復用,是 mux 的反操作,表示從一路輸入中分離出多路流(視頻、音頻、字幕等)。
mux 處理的是輸入格式,demux 處理的輸出格式。輸入/輸出媒體格式涉及文件格式和封裝格式兩個概念【封裝格式】 。
- 文件格式由文件擴展名標識【文件格式】,主要起提示作用,通過擴展名提示文件類型(或封裝格式)信息。封裝格式則是存儲媒體內容的實際容器格式,不同的封裝格式對應不同的文件擴展名,很多時候也用文件格式代指封裝格式,例如常用 ts 格式(文件格式)代指 mpegts 格式(封裝格式)。
- 例如,我們把 test.ts 改名為 test.mkv,mkv 擴展名提示了此文件封裝格式為 Matroska,但文件內容并無任何變化,使用 ffprobe 工具仍能正確探測出封裝格式為 mpegts。
2、查看 FFmpeg 支持的封裝格式
使用 ffmpeg -formats 命令可以查看 FFmpeg 支持的封裝格式。 FFmpeg 支持的封裝非常多, 下面僅列出最常用的幾種:
- h264/aac 裸流封裝格式
- h264 裸流封裝格式和 aac 裸流封裝格式在后面的解復用和復用例程中會用到,這里先討論一下。h264 本來是編碼格式,當作封裝格式時表示的是 H.264 裸流格式,所謂裸流就是不含封裝信息的流,也就是沒穿衣服的流。aac 等封裝格式類似。
看一下 FFmpeg 工程源碼中 h264 編碼格式以及 h264 封裝格式的定義:FFmpeg 工程包含 h264 解碼器,而不包含 h264 編碼器(一般使用第三方 libx264 編碼器用作 h264 編碼),所以只有解碼器定義:
AVCodec ff_h264_decoder = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC /MPEG-4 part 10"),.type = AVMEDIA_TYPE_VIDEO,.id = AV_CODEC_ID_H264,......
};
h264 封裝格式定義如下:
AVOutputFormat ff_h264_muxer = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),.extensions = "h264,264",.audio_codec = AV_CODEC_ID_NONE,.video_codec = AV_CODEC_ID_H264,.write_header = force_one_stream,.write_packet = ff_raw_write_packet,.check_bitstream = h264_check_bitstream,.flags = AVFMT_NOTIMESTAMPS,
};AVOutputFormat ff_h264_muxer = {.name = "h264",.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),.extensions = "h264,264",.audio_codec = AV_CODEC_ID_NONE,.video_codec = AV_CODEC_ID_H264,.write_header = force_one_stream,.write_packet = ff_raw_write_packet,.check_bitstream = h264_check_bitstream,.flags = AVFMT_NOTIMESTAMPS,
};
二、API 介紹
FFmpeg 中將編碼幀及未編碼幀均稱作 frame,本文為方便,將編碼幀稱作 packet,未編碼幀稱作 frame。
未壓縮的,未編碼的:原始圖片(yuv,rgb)或聲音(pcm 流)。
壓縮的, 編碼的:H264/265,aac/ac3/mp3
最主要的 API 有如下幾個:
avformat_open_input()
:這個函數會打開輸入媒體文件,讀取文件頭,將文件格式信息存儲在第一個參數AVFormatContext
中。avformat_find_stream_info()
:這個函數會讀取一段視頻文件數據并嘗試解碼,將取到的流信息填入AVFormatContext.streams
中。AVFormatContext.streams
是一個指針數組,數組大小是AVFormatContext.nb_streams
。av_read_frame()
:本函數用于解復用過程。本函數將存儲在輸入文件中的數據分割為多個 packet, 每次調用將得到一個 packet。 packet 可能是視頻幀、音頻幀或其他數據,解碼器只會解碼視頻幀或音頻幀,非音視頻數據并不會
被扔掉、從而能向解碼器提供盡可能多的信息。- 對于視頻來說,一個 packet 只包含一個視頻幀;
- 對于音頻來說,若是幀長固定的格式則一個 packet 可包含整數個音頻幀,若是幀長可變的格式則一個 packet 只包含一個音頻幀。
- 讀取到的 packet 每次使用完之后應調用
av_packet_unref(AVPacket *pkt)
清空 packet。否則會造成內存泄露。
av_write_frame()
:本函數用于復用過程,將 packet 寫入輸出媒體。- packet 交織是指:不同流的 packet 在輸出媒體文件中應嚴格按照 packet 中 dts 遞增的順序交錯存放。
- 本函數直接將 packet 寫入復用器(muxer),不會緩存或記錄任何 packet。本函數不負責不同流的 packet 交織問題。,由調用者負責。
- 如果調用者不愿處理 packet 交織問題,應調用
av_interleaved_write_frame()
替代本函數。
av_interleaved_write_frame()
:本函數用于復用過程, 將 packet 寫入輸出媒體。- 本函數將按需在內部緩存 packet,從而確保輸出媒體中不同流的 packet 能按照 dts 增長的順序正確交織。
avio_open()
:創建并初始化一個AVIOContext
,用于訪問輸出媒體文件。avformat_write_header()
:向輸出文件寫入文件頭信息。av_write_trailer()
:向輸出文件寫入文件尾信息。
三、 實戰 1:解封裝
1、原理講解
本例子實現的是將音視頻分離,例如將封裝格式為 FLV、MKV、MP4、AVI 等封裝格式的文件,將音頻、視頻讀取出來并打印。實現的過程,可以大致用如下圖表示:
2、示例源碼 1
兼容舊版本使用遍歷的方式查找給定媒體文件中音頻流或視頻流,未使用新版本的 FFmpeg 新增加的函數 av_find_best_stream()
#include <stdio.h>
extern "C" {#include <libavformat/avformat.h>
}/**
* @brief 將一個AVRational類型的分數轉換為double類型的浮點數
* @param r:r為一個AVRational類型的結構體變量,成員num表示分子,成員den表示分母,r的值即為(double)r.num / (double)r.den。用這種方法表示可以最大程度地避免精度的損失
* @return 如果變量r的分母den為0,則返回0(為了避免除數為0導致程序死掉);其余情況返回(double)r.num / (double)r.den
*/
static double r2d(AVRational r)
{return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}int main()
{//需要讀取的本地媒體文件相對路徑為 ./debug/test.mp4const char *path = "./debug/test.mp4";///av_register_all(); //初始化所有組件,只有調用了該函數,才能使用復用器和編解碼器。否則,調用函數avformat_open_input會失敗,無法獲取媒體文件的信息avformat_network_init(); //打開網絡流。這里如果只需要讀取本地媒體文件,不需要用到網絡功能,可以不用加上這一句AVDictionary *opts = NULL;//AVFormatContext是描述一個媒體文件或媒體流的構成和基本信息的結構體AVFormatContext *ic = NULL;//媒體打開函數,調用該函數可以獲得路徑為path的媒體文件的信息,并把這些信息保存到指針ic指向的空間中(調用該函數后會分配一個空間,讓指針ic指向該空間)int re = avformat_open_input(&ic, path, NULL, &opts);if (re != 0) //如果打開媒體文件失敗,打印失敗原因。比如,如果上面沒有調用函數av_register_all,則會打印“XXX failed!:Invaliddata found when processing input”{char buf[1024] = { 0 };av_strerror(re, buf, sizeof(buf) - 1);printf("open %s failed!:%s", path, buf);}else //打開媒體文件成功{printf("打開媒體文件 %s 成功!\n", path);//調用該函數可以進一步讀取一部分視音頻數據并且獲得一些相關的信息。//調用avformat_open_input之后,我們無法獲取到正確和所有的媒體參數,所以還得要調用avformat_find_stream_info進一步的去獲取。avformat_find_stream_info(ic, NULL);//調用avformat_open_input讀取到的媒體文件的路徑/名字printf("媒體文件名稱:%s\n", ic->filename);//視音頻流的個數,如果一個媒體文件既有音頻,又有視頻,則nb_streams的值為2。如果媒體文件只有音頻,則值為1printf("視音頻流的個數:%d\n", ic->nb_streams);//媒體文件的平均碼率,單位為bpsprintf("媒體文件的平均碼率:%lldbps\n", ic->bit_rate);printf("duration:%d\n", ic->duration);int tns, thh, tmm, tss;tns = (ic->duration) / AV_TIME_BASE;thh = tns / 3600;tmm = (tns % 3600) / 60;tss = (tns % 60);printf("媒體文件總時長:%d時%d分%d秒\n", thh, tmm, tss); //通過上述運算,可以得到媒體文件的總時長printf("\n");//通過遍歷的方式讀取媒體文件視頻和音頻的信息,//新版本的FFmpeg新增加了函數av_find_best_stream,也可以取得同樣的效果,但這里為了兼容舊版本還是用這種遍歷的方式for (int i = 0; i < ic->nb_streams; i++){AVStream *as = ic->streams[i];if (AVMEDIA_TYPE_AUDIO == as->codecpar->codec_type) //如果是音頻流,則打印音頻的信息{printf("音頻信息:\n");printf("index:%d\n", as->index); //如果一個媒體文件既有音頻,又有視頻,則音頻index的值一般為1。但該值不一定準確,所以還是得通過as->codecpar->codec_type判斷是視頻還是音頻printf("音頻采樣率:%dHz\n", as->codecpar->sample_rate); //音頻編解碼器的采樣率,單位為Hzif (AV_SAMPLE_FMT_FLTP == as->codecpar->format) //音頻采樣格式{printf("音頻采樣格式:AV_SAMPLE_FMT_FLTP\n");}else if (AV_SAMPLE_FMT_S16P == as->codecpar->format){printf("音頻采樣格式:AV_SAMPLE_FMT_S16P\n");}printf("音頻信道數目:%d\n", as->codecpar->channels); //音頻信道數目if (AV_CODEC_ID_AAC == as->codecpar->codec_id) //音頻壓縮編碼格式{printf("音頻壓縮編碼格式:AAC\n");}else if (AV_CODEC_ID_MP3 == as->codecpar->codec_id){printf("音頻壓縮編碼格式:MP3\n");}int DurationAudio = (as->duration) * r2d(as->time_base); //音頻總時長,單位為秒。注意如果把單位放大為毫秒或者微妙,音頻總時長跟視頻總時長不一定相等的printf("音頻總時長:%d時%d分%d秒\n", DurationAudio / 3600, (DurationAudio % 3600) / 60, (DurationAudio % 60)); //將音頻總時長轉換為時分秒的格式打印到控制臺上printf("\n");}else if (AVMEDIA_TYPE_VIDEO == as->codecpar->codec_type) //如果是視頻流,則打印視頻的信息{printf("視頻信息:\n");printf("index:%d\n", as->index); //如果一個媒體文件既有音頻,又有視頻,則視頻index的值一般為0。但該值不一定準確,所以還是得通過as->codecpar->codec_type判斷是視頻還是音頻printf("視頻幀率:%lffps\n", r2d(as->avg_frame_rate)); //視頻幀率,單位為fps,表示每秒出現多少幀if (AV_CODEC_ID_MPEG4 == as->codecpar->codec_id) //視頻壓縮編碼格式{printf("視頻壓縮編碼格式:MPEG4\n");}printf("幀寬度:%d 幀高度:%d\n", as->codecpar->width, as->codecpar->height); //視頻幀寬度和幀高度int DurationVideo = (as->duration) * r2d(as->time_base); //視頻總時長,單位為秒。注意如果把單位放大為毫秒或者微妙,音頻總時長跟視頻總時長不一定相等的printf("視頻總時長:%d時%d分%d秒\n", DurationVideo / 3600, (DurationVideo % 3600) / 60, (DurationVideo % 60)); //將視頻總時長轉換為時分秒的格式打印到控制臺上printf("\n");}}//av_dump_format(ic, 0, path, 0);}if (ic){avformat_close_input(&ic); //關閉一個AVFormatContext,和函數avformat_open_input()成對使用}avformat_network_deinit();return 0;
}
3、運行結果 1
打開媒體文件 ./debug/test.mp4 成功!
媒體文件名稱:./debug/test.mp4
視音頻流的個數:2
媒體文件的平均碼率:1436830bps
duration:117312000
媒體文件總時長:0時1分57秒視頻信息:
index:0
視頻幀率:25.000000fps
幀寬度:1280 幀高度:720
視頻總時長:0時1分57秒音頻信息:
index:1
音頻采樣率:48000Hz
音頻采樣格式:AV_SAMPLE_FMT_FLTP
音頻信道數目:6
音頻壓縮編碼格式:AAC
音頻總時長:0時1分57秒
使用 MediaInfo 打開分析可以看到與上面的打印信息是對應上的
4、示例源碼 2
使用新版本的 FFmpeg 新增加的函數 av_find_best_stream()
查找給定媒體文件中最佳的流(音頻流或視頻流)
#include <stdio.h>
extern "C"
{#include <libavformat/avformat.h>
}
int main(int argc, char **argv)
{
// 1. 打開文件const char *ifilename = "./debug/test.mp4";printf("in_filename = %s\n", ifilename);avformat_network_init();// AVFormatContext是描述一個媒體文件或媒體流的構成和基本信息的結構體AVFormatContext *ifmt_ctx = NULL; // 輸入文件的demux// 打開文件,主要是探測協議類型,如果是網絡文件則創建網絡鏈接int ret = avformat_open_input(&ifmt_ctx, ifilename, NULL, NULL);if (ret < 0) {char buf[1024] = {0};av_strerror(ret, buf, sizeof (buf) - 1);printf("open %s failed: %s\n", ifilename, buf);return -1;}// 2. 讀取碼流信息ret = avformat_find_stream_info(ifmt_ctx, NULL);if (ret < 0) //如果打開媒體文件失敗,打印失敗原因{char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);printf("avformat_find_stream_info %s failed:%s\n", ifilename, buf);avformat_close_input(&ifmt_ctx);return -1;}// 3.打印總體信息printf_s("\n==== av_dump_format in_filename:%s ===\n", ifilename);av_dump_format(ifmt_ctx, 0, ifilename, 0);printf_s("\n==== av_dump_format finish =======\n\n");printf("media name:%s\n", ifmt_ctx->url);printf("stream number:%d\n", ifmt_ctx->nb_streams); // nb_streams媒體流數量printf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024)); // 媒體文件的碼率,單位為bps/1000=Kbps// duration: 媒體文件時長,單位微妙int total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE; // 1000us = 1ms, 1000ms = 1秒printf("audio duration: %02d:%02d:%02d\n",total_seconds / 3600, (total_seconds % 3600) / 60, (total_seconds % 60));printf("\n");// 4.讀取碼流信息// 音頻int audioindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (audioindex < 0) {printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_AUDIO));return -1;}AVStream *audio_stream = ifmt_ctx->streams[audioindex];printf("----- Audio info:\n");printf("index: %d\n", audio_stream->index); // 序列號printf("samplarate: %d Hz\n", audio_stream->codecpar->sample_rate); // 采樣率printf("sampleformat: %d\n", audio_stream->codecpar->format); // 采樣格式 AV_SAMPLE_FMT_FLTP:8printf("audio codec: %d\n", audio_stream->codecpar->codec_id); // 編碼格式 AV_CODEC_ID_MP3:86017 AV_CODEC_ID_AAC:86018if (audio_stream->duration != AV_NOPTS_VALUE) {int audio_duration = audio_stream->duration * av_q2d(audio_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",audio_duration / 3600, (audio_duration % 3600) / 60, (audio_duration % 60));}// 視頻int videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (videoindex < 0) {printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));return -1;}AVStream *video_stream = ifmt_ctx->streams[videoindex];printf("----- Video info:\n");printf("index: %d\n", video_stream->index); // 序列號printf("fps: %lf\n", av_q2d(video_stream->avg_frame_rate)); // 幀率printf("width: %d, height:%d \n", video_stream->codecpar->width, video_stream->codecpar->height);printf("video codec: %d\n", video_stream->codecpar->codec_id); // 編碼格式 AV_CODEC_ID_H264: 27if (video_stream->duration != AV_NOPTS_VALUE) {int video_duration = video_stream->duration * av_q2d(video_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",video_duration / 3600, (video_duration % 3600) / 60, (video_duration % 60));}// 5.提取碼流AVPacket *pkt = av_packet_alloc();int pkt_count = 0;int print_max_count = 100;printf("\n-----av_read_frame start\n");while (1){ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0) {printf("av_read_frame end\n");break;}if(pkt_count++ < print_max_count){if (pkt->stream_index == audioindex){printf("audio pts: %lld\n", pkt->pts);printf("audio dts: %lld\n", pkt->dts);printf("audio size: %d\n", pkt->size);printf("audio pos: %lld\n", pkt->pos);printf("audio duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));}else if (pkt->stream_index == videoindex){printf("video pts: %lld\n", pkt->pts);printf("video dts: %lld\n", pkt->dts);printf("video size: %d\n", pkt->size);printf("video pos: %lld\n", pkt->pos);printf("video duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));}else{printf("unknown stream_index:\n", pkt->stream_index);}}av_packet_unref(pkt);}// 6.結束if(pkt)av_packet_free(&pkt);if(ifmt_ctx)avformat_close_input(&ifmt_ctx);avformat_network_deinit();return 0;
}
5、運行結果 2
in_filename = ./debug/test.mp4==== av_dump_format in_filename:./debug/test.mp4 ======= av_dump_format finish =======media name:./debug/test.mp4
stream number:2
media average ratio:1403kbps
audio duration: 00:01:57----- Audio info:
index: 1
samplarate: 48000 Hz
sampleformat: 8
audio codec: 86018
audio duration: 00:01:57
----- Video info:
index: 0
fps: 25.000000
width: 1280, height:720
video codec: 27
audio duration: 00:01:57-----av_read_frame start
audio pts: 0
audio dts: 0
audio size: 967
audio pos: 48
audio duration: 0.021333video pts: 0
video dts: 0
video size: 105222
video pos: 1015
video duration: 0.040000
....
audio pts: 65536
audio dts: 65536
audio size: 949
audio pos: 346354
audio duration: 0.021333video pts: 17408
video dts: 17408
video size: 6906
video pos: 347303
video duration: 0.040000
av_read_frame end
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':Metadata:major_brand : isomminor_version : 512compatible_brands: isomiso2avc1mp41creation_time : 1970-01-01T00:00:00.000000Zencoder : Lavf53.24.2Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/sStream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)Metadata:creation_time : 1970-01-01T00:00:00.000000Zhandler_name : VideoHandlerStream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)Metadata:creation_time : 1970-01-01T00:00:00.000000Zhandler_name : SoundHandler
四、 實戰 2:轉封裝
1、原理講解
從一種視頻容器轉成另一種視頻容器。
所謂的封裝格式轉換,就是在 AVI,FLV,MKV,MP4 這些格式之間轉換(對應 .avi
,.flv
,.mkv
,.mp4
文件)。需要注意的是,本程序并不進行視音頻的編碼和解碼工作。而是直接將視音頻壓縮碼流從一種封裝格式文件中獲取出來然后打包成另外一種封裝格式的文件。
傳統的轉碼程序工作原理如下圖所示:
上圖例舉了一個舉例:FLV(視頻:H.264,音頻:AAC)轉碼為 AVI(視頻:MPEG2,音頻 MP3)的例子。可見視頻轉碼的過程通俗地講相當于把視頻和音頻重新“錄” 了一遍。
本程序的工作原理如下圖所示:
由圖可見,本程序并不進行視頻和音頻的編解碼工作,因此本程序和普通的轉碼軟件相比,有以下兩個特點:
- 處理速度極快。視音頻編解碼算法十分復雜,占據了轉碼的絕大部分時間。因為不需要進行視音頻的編碼和解碼,所以節約了大量的時間。
- 視音頻質量無損。
- 因為不需要進行視音頻的編碼和解碼, 所以不會有視音頻的壓縮損傷。
程序流程圖如下圖:
2、示例源碼
以下源碼實現下面命令同樣的功能
ffmepg -i test.mp4 -c copy -f test.flv
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",tag,av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),pkt->stream_index);
}int main(int argc, char **argv)
{AVOutputFormat *ofmt = NULL;AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;AVPacket pkt;const char *in_filename, *out_filename;int ret, i;int stream_index = 0;int *stream_mapping = NULL;int stream_mapping_size = 0;in_filename = "./debug/test.mp4";out_filename = "./debug/test.flv";if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {fprintf(stderr, "Could not open input file '%s'", in_filename);goto end;}if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {fprintf(stderr, "Failed to retrieve input stream information");goto end;}printf("\n\n-------av_dump_format:ifmt_ctx----------------\n");av_dump_format(ifmt_ctx, 0, in_filename, 0);avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);if (!ofmt_ctx) {fprintf(stderr, "Could not create output context\n");ret = AVERROR_UNKNOWN;goto end;}stream_mapping_size = ifmt_ctx->nb_streams;stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));if (!stream_mapping) {ret = AVERROR(ENOMEM);goto end;}ofmt = ofmt_ctx->oformat;for (i = 0; i < ifmt_ctx->nb_streams; i++) {AVStream *out_stream;AVStream *in_stream = ifmt_ctx->streams[i];AVCodecParameters *in_codecpar = in_stream->codecpar;if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {stream_mapping[i] = -1;continue;}stream_mapping[i] = stream_index++;out_stream = avformat_new_stream(ofmt_ctx, NULL);if (!out_stream) {fprintf(stderr, "Failed allocating output stream\n");ret = AVERROR_UNKNOWN;goto end;}ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);if (ret < 0) {fprintf(stderr, "Failed to copy codec parameters\n");goto end;}out_stream->codecpar->codec_tag = 0;}printf("\n\n-------av_dump_format:ofmt_ctx----------------\n");av_dump_format(ofmt_ctx, 0, out_filename, 1);if (!(ofmt->flags & AVFMT_NOFILE)) {ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);if (ret < 0) {fprintf(stderr, "Could not open output file '%s'", out_filename);goto end;}}ret = avformat_write_header(ofmt_ctx, NULL);if (ret < 0) {fprintf(stderr, "Error occurred when opening output file\n");goto end;}while (1) {AVStream *in_stream, *out_stream;/// 讀取音視頻壓縮包ret = av_read_frame(ifmt_ctx, &pkt);if (ret < 0)break;in_stream = ifmt_ctx->streams[pkt.stream_index];if (pkt.stream_index >= stream_mapping_size ||stream_mapping[pkt.stream_index] < 0) {av_packet_unref(&pkt);continue;}pkt.stream_index = stream_mapping[pkt.stream_index];out_stream = ofmt_ctx->streams[pkt.stream_index];log_packet(ifmt_ctx, &pkt, "in");/* copy packet */pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, 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;log_packet(ofmt_ctx, &pkt, "out");/// 交織寫音視頻包ret = av_interleaved_write_frame(ofmt_ctx, &pkt);if (ret < 0) {fprintf(stderr, "Error muxing packet\n");break;}av_packet_unref(&pkt);//包需要解引用}av_write_trailer(ofmt_ctx);
end:avformat_close_input(&ifmt_ctx);/* close output */if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))avio_closep(&ofmt_ctx->pb);avformat_free_context(ofmt_ctx);av_freep(&stream_mapping);if (ret < 0 && ret != AVERROR_EOF) {fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));return 1;}return 0;
}
3、運行結果
-------av_dump_format:ifmt_ctx-----------------------av_dump_format:ofmt_ctx----------------
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:21 duration_time:0.021 stream_index:1
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:512 duration_time:0.04 stream_index:0
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:40 duration_time:0.04 stream_index:0
in: pts:1024 pts_time:0.0213333 dts:1024 dts_time:0.0213333 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:21 pts_time:0.021 dts:21 dts_time:0.021 duration:21 duration_time:0.021 stream_index:1
....
in: pts:1500672 pts_time:117.24 dts:1500672 dts_time:117.24 duration:512 duration_time:0.04 stream_index:0
out: pts:117240 pts_time:117.24 dts:117240 dts_time:117.24 duration:40 duration_time:0.04 stream_index:0
in: pts:5628928 pts_time:117.269 dts:5628928 dts_time:117.269 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117269 pts_time:117.269 dts:117269 dts_time:117.269 duration:21 duration_time:0.021 stream_index:1
in: pts:5629952 pts_time:117.291 dts:5629952 dts_time:117.291 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117291 pts_time:117.291 dts:117291 dts_time:117.291 duration:21 duration_time:0.021 stream_index:1
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':Metadata:major_brand : isomminor_version : 512compatible_brands: isomiso2avc1mp41creation_time : 1970-01-01T00:00:00.000000Zencoder : Lavf53.24.2Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/sStream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)Metadata:creation_time : 1970-01-01T00:00:00.000000Zhandler_name : VideoHandlerStream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)Metadata:creation_time : 1970-01-01T00:00:00.000000Zhandler_name : SoundHandler
Output #0, flv, to './debug/test.flv':Stream #0:0: Video: h264 (Main), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 1048 kb/sStream #0:1: Audio: aac (LC), 48000 Hz, 5.1, fltp, 383 kb/s
使用 MediaInfo 查看未進行轉封裝前 test.mp4 的封裝格式為 MPEG-4
使用 MediaInfo 查看轉封裝后新生成的 test.flv 文件的封裝格式為 Flash Video
我的qq:2442391036,歡迎交流!