、HTML代碼如下:
<div id="player"></div>
二、JavaScript代碼如下:
<script src="http://cdn.jsdelivr.net/npm/xgplayer/browser/index.js" charset="utf-8"></script> <script src="http://cdn.jsdelivr.net/npm/xgplayer-flv.js/browser/index.js" charset="utf-8"></script> <script type="text/javascript"> new window.FlvJsPlayer({ id: 'player', isLive: false, playsinline: true, url: '/test/test.flv', autoplay: true, height: window.innerHeight, width: window.innerWidth }); </script>
.環境準備
音視頻數據用代碼生成,不是從文件讀取,然后再合成為flv。
合成的flv。
播放如下:
2.FFMPEG合成音視頻框架
(1)avformat_alloc_output_context2:分配AVFormatContext,并根據filename綁定合適的AVOutputFormat。
(2)通過fmt->video_codec=AV_CODEC_ID_H264 和fmt->audio_codec=AV_CODEC_ID_AAC。
(3)通過add_stream,添加流,設置編碼器參數。通過自定義的函數open_video和open_audio,關聯編碼器,分配幀buffer,初始化scale,初始化PCM的參數,初始化重采樣器。
(4)通過avio_open,打開對應的輸出文件。
(5)avformat_write_header : 寫?件頭
(6)寫視頻幀和音頻幀,涉及到編碼音視頻,這里面也會涉及到time_base轉換。
(7)av_write_frame/av_interleaved_write_frame: 寫packet
(8)av_write_trailer : 寫?件尾,如果是實時流,可以不用寫文件尾。
看看這些比較重要的函數和結構:
avformat_write_header:
注意:這里會改變音視頻各自的time_base,如,開始audio的 AVstream->base_time=1/44100, video 為AVstream->base_time=1/25。經過avformat_write_header后,audio和video的time_base就變為1/1000。這種改變,是與封裝格式有關系。比如ts和flv又是不一樣。
上面圖中的紅框部分,就會調用到復用器的write_header,如:
avformat_alloc_output_context2:
函數在在libavformat.h??的定義。
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59
AVOutputFormat *oformat,const char *format_name, const char *filename);
ctx:需要創建的context,返回NULL表示失敗。
oformat:指定對應的AVOutputFormat,如果不指定,可以通過后?format_name、filename兩個參數進?指定,讓ffmpeg??推斷。
format_name: 指定?視頻的格式,?如“flv”,“mpeg”等,如果設置為NULL,則由filename進?指定,讓ffmpeg??推斷。
filename: 指定?視頻?件的路徑,如果oformat、format_name為NULL,則ffmpeg內部根據filename后綴名選擇合適的復?器,?如xxx.flv則使?flv復?器。
int avformat_alloc_output_context2(AVFormatContext **avctx, ff_const 59 AVOutputFormat *oformat, 2 const char *format, const char *f ilename) 3 { 4 AVFormatContext *s=avformat_alloc_context(); 5 int ret=0; 67 *avctx=NULL; 8 if (!s) 9 goto nomem; 1011 if (!oformat) { // oformat為NULL 12 if (format) { 13 oformat=av_guess_format(format, NULL, NULL); //根據提供 的格式進?查找 format 14 if (!oformat) { 15 av_log(s, AV_LOG_ERROR, "Requested output format '% s' is not a suitable output format\n", format); 16 ret=AVERROR(EINVAL); 17 goto error; 18 } 19 } else { // oformat和format都為NULL 20 oformat=av_guess_format(NULL, filename, NULL); // 根據 ?件名后綴進?查找 21 if (!oformat) { 22 ret=AVERROR(EINVAL); 23 av_log(s, AV_LOG_ERROR, "Unable to find a suitable o utput format for '%s'\n", 24 filename); 25 goto error; 26 } 27 } 28 } 2930 s->oformat=oformat; 31 if (s->oformat->priv_data_size > 0) { 32 s->priv_data=av_mallocz(s->oformat->priv_data_size); 33 if (!s->priv_data) 7
34 goto nomem; 35 if (s->oformat->priv_class) { 36 *(const AVClass**)s->priv_data=s->oformat->priv_class; 37 av_opt_set_defaults(s->priv_data); 38 } 39 } else 40 s->priv_data=NULL; 4142 if (filename) { 43 #if FF_API_FORMAT_FILENAME 44 FF_DISABLE_DEPRECATION_WARNINGS 45 av_strlcpy(s->filename, filename, sizeof(s->filename)); 46 FF_ENABLE_DEPRECATION_WARNINGS 47 #endif 48 if (!(s->url=av_strdup(filename))) 49 goto nomem; 5051 } 52 *avctx=s; 53 return 0; 54 nomem: 55 av_log(s, AV_LOG_ERROR, "Out of memory\n"); 56 ret=AVERROR(ENOMEM); 57 error: 58 avformat_free_context(s); 59 return ret; 60 }
可以看出,??最主要的就兩個函數,avformat_alloc_context和av_guess_format,?個是申請內存分配上下?,?個是通過后?兩個參數獲取AVOutputFormat。av_guess_format這個函數會通過filename和short_name來和所有的編碼器進??對,找出最接近的編碼器然后返回。
ff_const59 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
const char *mime_type)
{
const AVOutputFormat *fmt=NULL;
AVOutputFormat *fmt_found=NULL;
void *i=0;
int score_max, score;
/* specific test for image sequences */
#if CONFIG_IMAGE2_MUXER
if (!short_name && filename &&
av_filename_number_test(filename) &&
ff_guess_image2_codec(filename) !=AV_CODEC_ID_NONE) {
return av_guess_format("image2", NULL, NULL);
}
#endif
/* Find the proper file type. */
score_max=0;
while ((fmt=av_muxer_iterate(&i))) {
score=0;
if (fmt->name && short_name && av_match_name(short_name, fmt->name))
// fmt->name?如ff_flv_muxer的為"flv"
// 匹配了name 最?規格
score +=100;
if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
// ff_flv_muxer的為 "video/x-flv"
// 匹配mime_type
score +=10;
if (filename && fmt->extensions &&
av_match_ext(filename, fmt->extensions)) {
//ff_flv_muxe r的為 "flv"
// 匹配
score +=5;
}
if (score > score_max) {
// 更新最匹配的分值
score_max=score;
fmt_found=(AVOutputFormat*)fmt;
}
}
return fmt_found;
}
AVOutputFormat
AVOutpufFormat表示輸出?件容器格式,AVOutputFormat 結構主要包含的信息有:封裝名稱描述,編碼格式信息(video/audio 默認編碼格式,?持的編碼格式列表),?些對封裝的操作函數(write_header,write_packet,write_tailer等)。
ffmpeg?持各種各樣的輸出?件格式,MP4,FLV,3GP等等。? AVOutputFormat 結構體則保存了這些格式的信息和?些常規設置。每?種封裝對應?個 AVOutputFormat 結構,ffmpeg將AVOutputFormat 按照鏈表存儲:
2.結構體定義
typedef struct AVOutputFormat{
const char *name;
/**
* Descriptive name for the format, meant to be more human-readable
* than name. You should use the NULL_IF_CONFIG_SMALL() macro
* to define it.
*/
const char *long_name;
const char *mime_type;
const char *extensions; /**< comma-separated filename extensions */
/* output support */
enum AVCodecID audio_codec; /**< default audio codec */
enum AVCodecID video_codec; /**< default video codec */
enum AVCodecID subtitle_codec; /**< default subtitle codec */
/**
* can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER,
* AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS,
* AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH,
* AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE
*/
int flags;
/**
* List of supported codec_id-codec_tag pairs, ordered by "better
* choice first". The arrays are all terminated by AV_CODEC_ID_NONE.
*/
const struct AVCodecTag * const *codec_tag;
const AVClass *priv_class; ///< AVClass for the private context
/*****************************************************************
* No fields below this line are part of the public API. They
* may not be used outside of libavformat and can be changed and
* removed at will.
* New public fields should be added right above.
*****************************************************************
*/
/**
* The ff_const59 define is not part of the public API and will
* be removed without further warning.
*/
#if FF_API_AVIOFORMAT
#define ff_const59
#else
#define ff_const59 const
#endif
ff_const59 struct AVOutputFormat *next;
/**
* size of private data so that it can be allocated in the wrapper
*/
int priv_data_size;
int (*write_header)(struct AVFormatContext *);
/**
* Write a packet. If AVFMT_ALLOW_FLUSH is set in flags,
* pkt can be NULL in order to flush data buffered in the muxer.
* When flushing, return 0 if there still is more data to flush,
* or 1 if everything was flushed and there is no more buffered
* data.
*/
int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
int (*write_trailer)(struct AVFormatContext *);
/**
* Currently only used to set pixel format if not YUV420P.
*/
int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
AVPacket *in, int flush);
/**
* Test if the given codec can be stored in this container.
*
* @return 1 if the codec is supported, 0 if it is not.
* A negative number if unknown.
* MKTAG('A', 'P', 'I', 'C') if the codec is only supported as AV_DISPOSITION_ATTACHED_PIC
*/
int (*query_codec)(enum AVCodecID id, int std_compliance);
void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
int64_t *dts, int64_t *wall);
/**
* Allows sending messages from application to device.
*/
int (*control_message)(struct AVFormatContext *s, int type,
void *data, size_t data_size);
/**
* Write an uncoded AVFrame.
*
* See av_write_uncoded_frame() for details.
*
* The library will free *frame afterwards, but the muxer can prevent it
* by setting the pointer to NULL.
*/
int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
AVFrame **frame, unsigned flags);
/**
* Returns device list with it properties.
* @see avdevice_list_devices() for more details.
*/
int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
/**
* Initialize device capabilities submodule.
* @see avdevice_capabilities_create() for more details.
*/
int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
/**
* Free device capabilities submodule.
* @see avdevice_capabilities_free() for more details.
*/
int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
enum AVCodecID data_codec; /**< default data codec */
/**
* Initialize format. May allocate data here, and set any AVFormatContext or
* AVStream parameters that need to be set before packets are sent.
* This method must not write output.
*
* Return 0 if streams were fully configured, 1 if not, negative AVERROR on failure
*
* Any allocations made here must be freed in deinit().
*/
int (*init)(struct AVFormatContext *);
/**
* Deinitialize format. If present, this is called whenever the muxer is being
* destroyed, regardless of whether or not the header has been written.
*
* If a trailer is being written, this is called after write_trailer().
*
* This is called if init() fails as well.
*/
void (*deinit)(struct AVFormatContext *);
/**
* Set up any necessary bitstream filtering and extract any extra data needed
* for the global header.
* Return 0 if more packets from this stream must be checked; 1 if not.
*/
int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
}AVOutputFormat
3.常?變量及其作?
const char *name;// 復?器名稱
const char *long_name;//格式的描述性名稱,易于閱讀。
enum AVCodecID audio_codec; //默認的?頻編解碼器
enum AVCodecID video_codec; //默認的視頻編解碼器
enum AVCodecID subtitle_codec; //默認的字幕編解碼器
注意:?部分復?器都有默認的編碼器,所以?家如果要調整編碼器類型則需要???動指定。
?如AVOutputFormat的ff_flv_muxer,flv默認就指定了MP3
mpegts默認指定了,AV_CODEC_ID_MP2和AV_CODEC_ID_MPEG2VIDEO
int (*write_header)(struct AVFormatContext *);//寫頭
int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//寫?個數據包。 如果在標志中設
置AVFMT_ALLOW_FLUSH,則pkt可以為NULL。
int (*write_trailer)(struct AVFormatContext *);//寫尾部
//交叉寫包
int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush);
int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允許從應?程序向設備發送消息。
int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, AVFrame **frame,unsigned flags);//寫?個未編碼的AVFrame。
//初始化格式。 可以在此處分配數據,并設置在發送數據包之前需要設置的任何AVFormatContext或AVStream參數。
int (*init)(struct AVFormatContext *);
void (*deinit)(struct AVFormatContext *);//取消初始化格式。
int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//設置任何必要的?特流過濾,并提取全局頭部所需的任何額外數據。這個很關鍵。
avformat_new_stream
在 AVFormatContext 中創建 Stream 通道。
AVStream 即是流通道。例如我們將 H264 和 AAC 碼流存儲為MP4?件的時候,就需要在 MP4?件中增加兩個流通道,?個存儲Video:H264,?個存儲Audio:AAC。(假設H264和AAC只包含單個流通道)。
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
AVFormatContext :
unsigned int nb_streams; 記錄stream通道數?。
AVStream **streams; 存儲stream通道。
AVStream :
int index; 在AVFormatContext 中所處的通道索引
avformat_new_stream之后便在 AVFormatContext ?增加了 AVStream 通道(相關的index已經被設置了)。之后,我們就可以??設置 AVStream 的?些參數信息。例如 : codec_id , format ,bit_rate,width , height。相當于這些參數就有了“依靠”。
av_interleaved_write_frame
函數原型:int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)
功能:將數據包寫?輸出媒體?件,并確保正確的交織(保持packet dts的增?性)。該函數會在內部根據需要緩存packet,以確保輸出?件中的packet按dts遞增的順序正確交織。如果??進?交織則應調?av_write_frame()(沒有緩存)。如果沒有B幀的情況,正確設置pts后,這兩個接口表現出來的差不多。
參數:
返回值:成功時為0,錯誤時為負AVERROR。即使此函數調?失敗,Libavformat仍將始終釋放該packet。
av_compare_ts
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
返回值:
-1 ts_a 在ts_b之前。
1 ts_a 在ts_b之后。
0 ts_a 在ts_b同?位置。
MediaInfo分析?件寫?
這?只是分析avformat_write_header和av_write_trailer的作?。
avformat_write_header+ av_write_trailer。對于FLV??沒有任何變化。
mp4
時間戳詳解
參考原文地址:https://www.cnblogs.com/leisure_chn/p/10584910.html
I幀/P幀/B幀
I幀:I幀(Intra-coded picture, 幀內編碼幀,常稱為關鍵幀)包含?幅完整的圖像信息,屬于幀內編碼圖像,不含運動?量,在解碼時不需要參考其他幀圖像。因此在I幀圖像處可以切換頻道,?不會導致圖像丟失或?法解碼。I幀圖像?于阻?誤差的累積和擴散。在閉合式GOP中,每個GOP的第?個幀?定是I幀,且當前GOP的數據不會參考前后GOP的數據。
P幀:P幀(Predictive-coded picture, 預測編碼圖像幀)是幀間編碼幀,利?之前的I幀或P幀進?預測編碼。
B幀:B幀(Bi-directionally predicted picture, 雙向預測編碼圖像幀)是幀間編碼幀,利?之前和(或)之后的I幀或P幀進?雙向預測編碼。B幀不可以作為參考幀。B幀具有更?的壓縮率,但需要更多的緩沖時間以及更?的CPU占?率,因此B幀適合本地存儲以及視頻點播,?不適?對實時性要求較?的直播系統。
DTS和PTS
DTS(Decoding Time Stamp, 解碼時間戳),表示壓縮幀的解碼時間。
PTS(Presentation Time Stamp, 顯示時間戳),表示將壓縮幀解碼后得到的原始幀的顯示時間。
?頻中DTS和PTS是相同的。視頻中由于B幀需要雙向預測,B幀依賴于其前和其后的幀,因此含B幀的視頻解碼順序與顯示順序不同,即DTS與PTS不同。當然,不含B幀的視頻,其DTS和PTS是相同的。下圖以?個開放式GOP示意圖為例,說明視頻流的解碼順序和顯示順序。
采集順序:指圖像傳感器采集原始信號得到圖像幀的順序。
編碼順序:指編碼器編碼后圖像幀的順序。存儲到磁盤的本地視頻?件中圖像幀的順序與編碼順序相同。
傳輸順序:指編碼后的流在?絡中傳輸過程中圖像幀的順序。
解碼順序:指解碼器解碼圖像幀的順序。
顯示順序:序指圖像幀在顯示器上顯示的順序。
注意:從圖中可以看出,采集順序與顯示順序相同,編碼順序,傳輸順序和解碼順序相同。
以圖中“B[1]”幀為例進?說明,“B[1]”幀解碼時需要參考“I[0]”幀和“P[3]”幀,因此“P[3]”幀必須?“B[1]”幀先解碼。這就導致了解碼順序和顯示順序的不?致,后顯示的幀需要先解碼。
時間基與時間戳
在FFmpeg中,時間基(time_base)是時間戳(timestamp)的單位,時間戳值乘以時間基,可以得到實際的時刻值(以秒等為單位)。例如,如果?個視頻幀的dts是40,pts是160,其time_base是1/1000秒,那么可以計算出此視頻幀的解碼時刻是40毫秒(40/1000),顯示時刻是160毫秒(160/1000)。FFmpeg中時間戳(pts/dts)的類型是int64_t類型,把?個time_base看作?個時鐘脈沖,則可把dts/pts看作時鐘脈沖的計數。
三種時間基tbr、tbn和tbc
不同的封裝格式具有不同的時間基。在FFmpeg處理?視頻過程中的不同階段,也會采?不同的時間基。
FFmepg中有三種時間基,命令?中tbr、tbn和tbc的打印值就是這三種時間基的倒數。如下:
tbn:對應容器中的時間基。值是AVStream.time_base的倒數。
tbc:對應編解碼器中的時間基。值是AVCodecContext.time_base的倒數
tbr:從視頻流中猜算得到,可能是幀率或場率(幀率的2倍)。
測試?件下載(右鍵另存為):tnmil3.flv。
使?ffprobe探測媒體?件格式,如下:
關于tbr、tbn和tbc的說明,原?如下,來?FFmpeg郵件列表:
There are three different time bases for time stamps in FFmpeg.
The values printed are actually reciprocals of these, i.e. 1/tbr, 1/tbn and 1/tbc
tbn is the time base in AVStream that has come from the container,
I think. It is used for all AVStream time stamps。
tbc is the time base in AVCodecContext for the codec used for a particular stream.
It is used for all AVCodecContext and related time stamps.
內部時間基AV_TIME_BASE
除以上三種時間基外,FFmpeg還有?個內部時間基AV_TIME_BASE(以及分數形式的AV_TIME_BASE_Q)。
AV_TIME_BASE及AV_TIME_BASE_Q?于FFmpeg內部函數處理,使?此時間基計算得到時間值表示的是微秒。
時間值形式轉換
av_q2d()將時間從AVRational形式轉換為double形式。AVRational是分數類型,double是雙精度浮點數類型,轉換的結果單位是秒。轉換前后的值基于同?時間基,僅僅是數值的表現形式不同?已。
av_q2d()實現如下:
av_q2d()使??法如下:
注意:時刻表示的是一個瞬間值,時長表示能持續的時間。
時間基轉換函數
av_rescale_q()?于不同時間基的轉換,?于將時間值從?種時間基轉換為另?種時間基。
將a數值由 bq時間基轉成 cq的時間基,通過返回結果獲取以cq時間基表示的新數值。如下:
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)。這個函數的用法就更復雜,但是一般你可以選擇不用。
它的作?是計算 "a * b / c" 的值并分五種?式來取整。按照如下方式:
// Round toward zero(可以理解為向下取整). 趨近于0, round(2.5) 為 2, ?round(-2.5) 為 -2
AV_ROUND_ZERO=0,
// Round away from zero(可以理解為向上取整)。 趨遠于0 round(3.5)=4, round(-3.5)=-4
AV_ROUND_INF=1,
// Round toward -infinity.向負?窮??向 [-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-3, -2, 2, 5, 7, 2]
AV_ROUND_DOWN=2,
// Round toward +infinity. 向正?窮??向[-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-2, -1, 3, 6, 7, 3]
AV_ROUND_UP=3,
// 四舍五?,?于0.5取值趨向0,?于0.5取值趨遠于0
AV_ROUND_NEAR_INF=5,
av_packet_rescale_ts()?于將AVPacket中各種時間值從?種時間基轉換為另?種時間基。
轉封裝過程中的時間基轉換
容器中的時間基(AVStream.time_base,3.2節中的tbn)定義如下:
注意:AVStream.time_base是AVPacket中pts和dts的時間單位,輸?流與輸出流中time_base按如下?式確定:
對于輸?流:打開輸??件后,調?avformat_find_stream_info()獲取到每個流中的time_base。
對于輸出流:打開輸出?件后,調?avformat_write_header()可根據輸出?件封裝格式確定每個流的time_base并寫?輸出?件中。
不同封裝格式具有不同的時間基,在轉封裝(將?種封裝格式轉換為另?種封裝格式)過程中,時間基轉換相關代碼如下:
一般在以下都是一段固定的代碼,在推流前,一般都是這么寫的:
av_read_frame(ifmt_ctx, &pkt);
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_s tream->time_base);
下?的代碼具有和上?代碼相同的效果:
// 從輸??件中讀取packet
av_read_frame(ifmt_ctx, &pkt);
// 將packet中的各時間值從輸?流封裝格式時間基轉換到輸出流封裝格式時間基
av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_bas e);
這?流?的時間基 in_stream->time_base 和 out_stream->time_base ,是容器中的時間基,就是上面所說的tbn。
例如,flv封裝格式的time_base為{1,1000},ts封裝格式的time_base為{1,90000}。
我們編寫程序將flv封裝格式轉換為ts封裝格式,抓取原?件(flv)的前四幀顯示時間戳:
再抓取轉換的?件(ts)的前四幀顯示時間戳:
可以發現,對于同?個視頻幀,它們時間基(tbn)不同因此時間戳(pkt_pts)也不同,但是計算出來的時刻值(pkt_pts_time)是相同的。
比如,第一幀這樣計算。
看第?幀的時間戳,計算關系:80×{1,1000}==7200×{1,90000}==0.080000。
一定要記住一個結論就是,時間戳可以不一樣,但是時刻值肯定是一樣。
轉碼過程中的時間基轉換
編解碼器中的時間基(AVCodecContext.time_base,3.2節中的tbc)定義如下:
上述注釋指出,AVCodecContext.time_base是幀率(視頻幀)的倒數,每幀時間戳遞增1,那么tbc就等于幀率。編碼過程中,應由?戶設置好此參數。解碼過程中,此參數已過時,建議直接使?幀率倒數?作時間基。
這?有?個問題:按照此處注釋說明,幀率為25的視頻流,tbc理應為25,但實際值卻為50,不知作何解釋?是否tbc已經過時,不具參考意義?
所以這個結論就是:
根據注釋中的建議,實際使?時,在視頻解碼過程中,我們不使?AVCodecContext.time_base,??幀率倒數作時間基,在視頻編碼過程中,我們將AVCodecContext.time_base設置為幀率的倒數。
視頻流
視頻按幀播放,所以解碼后的原始視頻幀時間基為 1/framerate。
視頻解碼過程中的時間基轉換處理,packet的pts到底什么,要看實際的情況,從av_read_frame讀取的packet,是以AVSteam->time_base,送給解碼器之前沒有必要轉成AVcodecContext->time_base。
需要注意的是avcodec_receive_frame后以AVSteam->time_base為單位即可。
也就是以下這段代碼,沒有必要進行時間基的轉換。
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;
// 從輸??件中讀取編碼幀
av_read_frame(ifmt_ctx, &packet);
// 時間基轉換
int raw_video_time_base=av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_video_time_ba se);
// 解碼
avcodec_send_packet(dec_ctx, packet);
avcodec_receive_frame(dec_ctx, frame);
視頻編碼過程中的時間基轉換處理。編碼的時候frame如果以AVstream為time_base送編碼器,則avcodec_receive_packet讀取的時候也是可以轉成AVSteam->time_base,也就是自動可以轉換。也就是具體情況,具體分析。
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;
// 編碼
avcodec_send_frame(enc_ctx, frame);
//這里內部就可以轉換AVSteam->time_base
avcodec_receive_packet(enc_ctx, packet);
// 時間基轉換
packet.stream_index=out_stream_idx;
enc_ctx->time_base=av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_ base);
// 將編碼幀寫?輸出媒體?件
av_interleaved_write_frame(o_fmt_ctx, &packet);
?頻流
對于?頻流也是類似的,本質來講就是具體情況具體分析,?如ffplay 解碼播放時就是AVSteam的time_base為基準的packet。然后出來的frame再?AVSteam的time_base對應的將pts轉成秒(使用內部時間基轉換)。
但是要注意的是ffplay做了?個?較隱秘的設置:avctx->pkt_timebase=ic->streams[stream_index]->time_base; 即是對應的codeccontext??對pkt_timebase設置,和AVStream?樣的time_base。
?頻按采樣點播放,所以解碼后的原始?頻幀時間基為 1/sample_rate。
?頻解碼過程中的時間基轉換處理:
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;
// 從輸??件中讀取編碼幀
av_read_frame(ifmt_ctx, &packet);
// 時間基轉換
int raw_audio_time_base=av_inv_q(dec_ctx->sample_rate);
10 av_packet_rescale_ts(packet, in_stream->time_base, raw_audio_time_ba se);
11 // 解碼
12 avcodec_send_packet(dec_ctx, packet)
13 avcodec_receive_frame(dec_ctx, frame);
?頻編碼過程中的時間基轉換處理:
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;
// 編碼
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);
// 時間基轉換
packet.stream_index=out_stream_idx;
enc_ctx->time_base=av_inv_q(dec_ctx->sample_rate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_ base);
// 將編碼幀寫?輸出媒體?件
av_interleaved_write_frame(o_fmt_ctx, &packet);
3.分析實戰代碼
封裝音視頻編碼相關的數據結構。包括編碼器上下文,每一個stream,音頻的采樣數量,重采樣前后的frame等,詳細看如下圖:
定義AVOutputFormat *fmt,輸出文件容器格式, 封裝了復用規則,比如,ff_flv_muxer。
分配AVFormatContext并根據filename綁定合適的AVOutputFormat,如果如果不能根據文件后綴名找到合適的格式,那缺省使用flv格式。并獲取一些參數進行填充到輸出結構里。如下:
使用指定的音視頻編碼格式增加音頻流和視頻流,如果不想要音頻或視頻,那這個fmt->video_codec可以指定為AV_CODEC_ID_NONE。
這個add_stream具體是做什么工作呢?
查找編碼器,然后新建碼流,并綁定到 AVFormatContext。
如圖,這樣好理解點。
現在指定編碼器上下文的索引,默認索引值為-1,每次調用avformat_new_stream的時候nb_streams+1。但id是從0開始, 比如第1個流:對應流id=nb_streams(1) -1=0,第2個流:對應流id=nb_streams(2) -1=1。
緊接著就是創建編碼器上下文。
初始化音視頻編碼器的一些參數。
初始化音頻參數:
初始化視頻參數:
添加完流后,就要去open_video,看看具體做什么工作?
首先就是關聯編碼器,分配幀buffer,這里使用的是32字節對齊,如果編碼器格式需要的數據不是 AV_PIX_FMT_YUV420P才需要 調用圖像scale,一般H264是支持,需要轉換。還需要把編碼器上下文的一些參數拷貝過來。
添加完流后,也要去open_audio,看看具體做什么工作?
也要關聯編碼器,初始化PCM參數。配送給編碼器的幀, 并申請對應的buffer。也需要拷貝參數,如果采樣格式不符合要求,也是要創建音頻重采樣。
注意:這里會設置codec_ctx->time_base,這里就為1/44100。
做了前面的一些關聯,參數設置后。接下來就要打開對應輸出文件。
再開始就是寫入頭部,注意這里只是調用了一次。在寫入頭部是,對應steam的time_base被改寫,時間發生變化。
重頭戲,這里就開始循環寫入音頻和視頻數據了。其中av_compare_ts,就是用來比較音頻pts和視頻pts大小,做一個同步處理,使得pts都有序,以音頻pts為基準。
看看write_audio_frame,具體工作干了什么?
獲取音頻數據,進行格式轉換,時間基數轉換,添加pts。
這里通過av_compare_ts,控制了一個音頻編碼寫入時長。
然后再編碼音頻,再寫pkt到文件里。
看看write_video_frame,又主要干了什么?
主要就是獲取視頻數據,編碼視頻,然后寫PKT到文件(同樣也只產生5s中的數據)。
獲取視頻數據,進行像素格式轉換。
在 write_frame這里,會進行一個時間基數再次轉換。
pts_before * 1/44100=pts_after *1/1000。
pts_after=pts_before * 1/44100 * 1000=-1024 * 1/44100 * 1000=-23。相當于要存儲一個在文件中,合適的時間。也不要認為這里的單位一定是ms。一般我在這里,會根據duration去計算pts。
做了轉換后,新的pts,dts,duration,都會做轉換。
再看看video 的pts變化。
所以前面的工作都執行完后,需要調用av_write_trailer,MP4和FLV是有很大區別,如果MP4不調用這個函數,最后是無法播放。
以上這篇文章分析到這里,歡迎關注,點贊,轉發,收藏。如果需要測試代碼,可以私信。
lv.js 是 HTML5 Flash 視頻(FLV)播放器,純原生 JavaScript 開發,沒有用到 Flash。由 bilibili 網站開源。
引入js
<script src="flv.min.js"></script>
視圖
<div class="main">
<video id="videoElement" class="centeredVideo" controls autoplay width="1024" height="576">Your browser is too old which doesn't support HTML5 video.</video>
</div>
<br>
<div class="controls">
<!--<button onclick="flv_load()">加載</button>-->
<button onclick="flv_start()">開始</button>
<button onclick="flv_pause()">暫停</button>
<button onclick="flv_destroy()">停止</button>
<input style="width:100px" type="text" name="seekpoint" />
<button onclick="flv_seekto()">跳轉</button>
</div>
js
*請認真填寫需求信息,我們會在24小時內與您取得聯系。