最簡單的iOS 推流代碼,視頻捕獲,軟編碼(faac,x264),硬編碼(aac,h264),美顏,flv編碼,rtmp協議,陸續更新代碼解析,你想學的知識這裏都有,願意懂直播技術的同窗快來看!!git
源代碼:https://github.com/hardman/AWLivegithub
前文介紹瞭如何獲取音視頻的aac/h264數據,那麼如何將數據寫入rtmp流中呢? rtmp最初是Adobe Flash用於音視頻播放的一個實時傳輸協議。而flv正是Adobe推出的一個視頻格式,所以rtmp協議支持flv視頻流。 這裏能夠咱們把獲取的aac/h264的數據,直接轉成flv格式的視頻幀,而後按照時間戳依次發送給服務端便可。api
#flv格式簡介數組
flv整體來講是一個簡單的視頻格式,它包含2部分:header 和 body。緩存
header是固定格式的數據,表示本文件是一個flv文件。 header的長度是9個字節。bash
header後面緊跟着body數據。body是由一個一個稱爲的tag數據組成。 tag其實就是一個固定格式的數據塊,構造方式同header相似,只是叫法不一樣而已。服務器
tag分爲3種。script tag,video tag,audio tag。 script tag是flv的第一個tag,用於放一些視頻信息的,好比duration,width,height等。script tag對於flv格式的視頻文件比較重要,對於rtmp來講,能夠不寫入script tag。 video tag是視頻數據的封裝,也就是咱們獲取的h264數據基礎之上,增長一些flv特定的數據。 audio tag同video tag相似,是acc數據的封裝。架構
#代碼解析ide
flv相關代碼在 aw_encode_flv.h和aw_encode_flv.c中。 此模塊提供了flv編碼(aac+h264)功能。函數
這個模塊的暴露給外部的api爲2部分:
//一部分是建立flv的方法
//寫入header
extern void aw_write_flv_header(aw_data **flv_data);
//寫入flv tag
extern void aw_write_flv_tag(aw_data **flv_data, aw_flv_common_tag *common_tag);
//第二部分是全部tag的構造
//script tag
extern aw_flv_script_tag *alloc_aw_flv_script_tag();
extern void free_aw_flv_script_tag(aw_flv_script_tag **);
//audio tag
extern aw_flv_audio_tag *alloc_aw_flv_audio_tag();
extern void free_aw_flv_audio_tag(aw_flv_audio_tag **);
//video tag
extern aw_flv_video_tag *alloc_aw_flv_video_tag();
extern void free_aw_flv_video_tag(aw_flv_video_tag **);
複製代碼
外部使用時,可根據具體數據先建立不一樣的tag,填充好各個數據,而後使用aw_write_flv_tag方法將tag寫入aw_data中。 可用上述方法能夠構造出完整的flv文件。
##aw_data aw_data是爲了方便文件數據的讀取/寫入和管理而建立的工具模塊。 此模塊已處理了大端小端差別,可以讓文件讀寫更加方便快捷。 相關代碼在aw_data.h / aw_data.c中。
##flv header
extern void aw_write_flv_header(aw_data **flv_data){
uint8_t
f = 'F', l = 'L', v = 'V',//FLV
version = 1,//固定值
av_flag = 5;//5表示av,5表示只有a,1表示只有v
uint32_t flv_header_len = 9;//header固定長度爲9
data_writer.write_uint8(flv_data, f);
data_writer.write_uint8(flv_data, l);
data_writer.write_uint8(flv_data, v);
data_writer.write_uint8(flv_data, version);
data_writer.write_uint8(flv_data, av_flag);
data_writer.write_uint32(flv_data, flv_header_len);
//first previous tag size 根據flv協議,每一個tag後要寫入當前tag的size,稱爲previous tag size,header後面須要寫入4字節空數據。
data_writer.write_uint32(flv_data, 0);
}
複製代碼
##flv body
注意 若是是要構造flv文件,寫入header以後就能夠寫入script tag了。 若是是使用rtmp協議,則無需構造header,也無需script tag。可直接寫入 video tag和audio tag。 若使用rtmp協議必須在首幀寫入AVCDecoderConfigurationRecord (包含sps pps數據)和 AudioSpecificConfig,不然服務端沒法正常解析音視頻數據。
flv的body是由一個接一個的tag構成的。 一個flv tag分爲3部分:tag header + tag body + tag data size。
extern void aw_write_flv_tag(aw_data **flv_data, aw_flv_common_tag *common_tag){
//寫入header
aw_write_tag_header(flv_data, common_tag);
//寫入body
aw_write_tag_body(flv_data, common_tag);
//寫入data size
aw_write_tag_data_size(flv_data, common_tag);
}
複製代碼
###tag header
static void aw_write_tag_header(aw_data **flv_data, aw_flv_common_tag *common_tag){
//header 長度爲固定11個字節
//寫入tag type,video:9 audio:8 script:18
data_writer.write_uint8(flv_data, common_tag->tag_type);
//寫入body的size(data_size爲整個tag的長度)
data_writer.write_uint24(flv_data, common_tag->data_size - 11);
//寫入時間戳
data_writer.write_uint24(flv_data, common_tag->timestamp);
data_writer.write_uint8(flv_data, common_tag->timestamp_extend);
//寫入stream id爲0
data_writer.write_uint24(flv_data, common_tag->stream_id);
}
複製代碼
static void aw_write_script_tag_body(aw_data **flv_data, aw_flv_script_tag *script_tag){
//script tag寫入規則爲:類型-內容-類型-內容...類型-內容
//類型是1個字節整數,可取12種值:
// 0 = Number type
// 1 = Boolean type
// 2 = String type
// 3 = Object type
// 4 = MovieClip type
// 5 = Null type
// 6 = Undefined type
// 7 = Reference type
// 8 = ECMA array type
// 10 = Strict array type
// 11 = Date type
// 12 = Long string type
// 好比:若是類型是字符串,那麼先寫入1個字節表類型的2。另,寫入真正的字符串前,須要寫入2個字節的字符串長度。
// data_writer.write_string可以在寫入字符串前,先寫入字符串長度,此函數第三個參數表示用多少字節來存儲字符串長度。
// script tag 的結構基本上是固定的,首先寫入一個字符串: onMetaData,而後寫入一個數組。
// 寫入數組須要先寫入數組編號1字節:8,而後寫入數組長度4字節:11。
// 數組同OC的Dictionary相似,可寫入一個字符串+一個value。
// 因此每一個數組元素可先寫入一個字符串,而後寫入一個Number Type,再寫入具體的數值。
// 結束時需寫入3個字節的0x000009表示數組結束。
// 下面代碼中的duration/width/filesize均遵循此規則。
//2表示類型,字符串
data_writer.write_uint8(flv_data, 2);
data_writer.write_string(flv_data, "onMetaData", 2);
//數組類型:8
data_writer.write_uint8(flv_data, 8);
//數組長度:11
data_writer.write_uint32(flv_data, 11);
//寫入duration 0表示double,1表示uint8
data_writer.write_string(flv_data, "duration", 2);
data_writer.write_uint8(flv_data, 0);
data_writer.write_double(flv_data, script_tag->duration);
//寫入width
data_writer.write_string(flv_data, "width", 2);
data_writer.write_uint8(flv_data, 0);
data_writer.write_double(flv_data, script_tag->width);
...
...
...
//寫入file_size
data_writer.write_string(flv_data, "filesize", 2);
data_writer.write_uint8(flv_data, 0);
data_writer.write_double(flv_data, script_tag->file_size);
//3字節的0x9表示數組結束
data_writer.write_uint24(flv_data, 9);
}
複製代碼
###video tag body
static void aw_write_video_tag_body(aw_data **flv_data, aw_flv_video_tag *video_tag){
// video tag body 結構是這樣的:
// frame_type(4bit) + codec_id(4bit) + h264_package_type(8bit) + h264_composition_time(24bit) + video_tag_data(many bits)
// frame_type 表示是否關鍵幀,關鍵幀爲1,非關鍵幀爲2(固然還有更多取值,請參考[flv協議](https://wuyuans.com/img/2012/08/video_file_format_spec_v10.rar)
// codec_id 表示視頻協議:h264是7 h263是2。
// h264_package_type表示視頻幀數據的類型,2種取值:sequence header(也就是前面說的 sps pps 數據,rtmp要求首幀發送此數據,也稱爲AVCDecoderConfigurationRecord),另外一種爲nalu,正常的h264視頻幀。
// h264_compsition_time:cts是pts與dts的差值,flv中的timestamp表示的應該是pts。若是h264數據中不包含B幀,那麼此數據可傳0。
// video_tag_data 即純264數據。
uint8_t video_header = 0;
video_header |= video_tag->frame_type << 4 & 0xf0;
video_header |= video_tag->codec_id;
data_writer.write_uint8(flv_data, video_header);
if (video_tag->codec_id == aw_flv_v_codec_id_H264) {
data_writer.write_uint8(flv_data, video_tag->h264_package_type);
data_writer.write_uint24(flv_data, video_tag->h264_composition_time);
}
switch (video_tag->h264_package_type) {
case aw_flv_v_h264_packet_type_seq_header: {
data_writer.write_bytes(flv_data, video_tag->config_record_data->data, video_tag->config_record_data->size);
break;
}
case aw_flv_v_h264_packet_type_nalu: {
data_writer.write_bytes(flv_data, video_tag->frame_data->data, video_tag->frame_data->size);
break;
}
case aw_flv_v_h264_packet_type_end_of_seq: {
//nothing
break;
}
}
}
複製代碼
###audio tag body
static void aw_write_audio_tag_body(aw_data **flv_data, aw_flv_audio_tag *audio_tag){
// audio tag body的結構是這樣的:
// sound_format(4bit) + sound_rate(sample_rate)(2bit) + sound_size(sample_size)(1bit) + sound_type(1bit) + aac_packet_type(8bit) + aac_data(many bits)
// sound_format 表示聲音格式,2表示mp3,10表示aac,通常是aac
// sound_rate 採樣率,表示1秒鐘採集多少個樣本,可選4個值,0表示5.5kHZ,1表示11kHZ,2表示22kHZ,3表示44kHZ,通常是3。
// sound_size 採樣尺寸,單個樣本的size。2個選擇,0表示8bit,1表示16bit。
// 直觀上看,採樣率和採樣尺寸應該和質量有必定關係。採樣率高,採樣尺寸大效果應該會好,可是生成的數據量也大。
// sound_type 表示聲音類型,0表示單聲道,1表示立體聲。(立體聲有2條聲道)。
// aac_packet_type表示aac數據類型,有2種選擇:0表示sequence header,即 必須首幀發送的數據(AudioSpecificConfig),1表示正常的aac數據。
uint8_t audio_header = 0;
audio_header |= audio_tag->sound_format << 4 & 0xf0;
audio_header |= audio_tag->sound_rate << 2 & 0xc;
audio_header |= audio_tag->sound_size << 1 & 0x2;
audio_header |= audio_tag->sound_type & 0x1;
data_writer.write_uint8(flv_data, audio_header);
if (audio_tag->sound_format == aw_flv_a_codec_id_AAC) {
data_writer.write_uint8(flv_data, audio_tag->aac_packet_type);
}
switch (audio_tag->aac_packet_type) {
case aw_flv_a_aac_package_type_aac_sequence_header: {
data_writer.write_bytes(flv_data, audio_tag->config_record_data->data, audio_tag->config_record_data->size);
break;
}
case aw_flv_a_aac_package_type_aac_raw: {
data_writer.write_bytes(flv_data, audio_tag->frame_data->data, audio_tag->frame_data->size);
break;
}
}
}
複製代碼
根據flv協議,每一個flv tag結束時,須要寫入此tag的所有長度:header+body的長度,header長度固定爲11字節,而body的長度可經過上面構造body時寫入的數據進行計算。
static void aw_write_tag_data_size(aw_data **flv_data, aw_flv_common_tag *common_tag){
data_writer.write_uint32(flv_data, common_tag->data_size);
}
複製代碼
上面的data_size由外部使用此模塊的函數,在建立tag時計算出來的。 能夠看aw_sw_faac_encoder.c中的aw_encoder_create_audio_tag方法:
extern aw_flv_audio_tag *aw_encoder_create_audio_tag(int8_t *aac_data, long len, uint32_t timeStamp, aw_faac_config *faac_cfg){
aw_flv_audio_tag *audio_tag = aw_sw_encoder_create_flv_audio_tag(faac_cfg);
...
...
//此處計算的data_size長度爲 11(tag header size) + body header size(即下面的header_size,表示body中除去aac data的部分) + aac data size
audio_tag->common_tag.data_size = audio_tag->frame_data->size + 11 + audio_tag->common_tag.header_size;
return audio_tag;
}
複製代碼
這是本項目的處理方式。固然data size也能夠在寫入header和body時,同步計算出來。
flv的tag中有2個字段表示時間戳,一個是 timestamp(pts),一個是Composition Time(cts)。 pts表示展現時間戳,表示這一幀何時展現。 說cts以前有必要介紹一下dts,dts表示解碼時間戳。 咱們知道h264中有3種視頻幀,I幀,P幀,B幀。 I和P幀沒必要說。 由於B幀的存在,可能會令後面的視頻幀先於前面的視頻幀解析,這樣就須要在視頻幀信息中保存dts。 flv中的cts能夠作這件事情,cts = pts - dts。
另外一個問題是,rtmp中的flv時間戳有一個規則就是,音頻+視頻幀須按照pts遞增順序發送。 由於音頻和視頻有各自的幀率,每一個音視頻幀可計算出各自的時間戳。 因爲音頻和視頻在不一樣的線程中編碼,編碼後的音視頻會合併到相同的線程中發送。 由於編碼速度等各類緣由,編碼後的數據合併到相同線程時,可能並非按照時間戳升序排列的。
爲了保證排序,有2種辦法解決此問題:
##推流時保存發送的flv文件 根據本文介紹,咱們能夠把發送到rtmp服務器的數據保存到本地flv文件。 能夠修改aw_streamer.c文件。
至此,flv編碼介紹完畢。