問題場景:
????????使用GPU編碼(Opencv)生成的h264視頻片段中不包含時間戳信息,且含有B幀,直接合成mp4后會導致播放出現(xiàn)問題(瞬間播放完成)。因此,在合成時需要手動添加時間戳。
心路歷程:
? ? ? ? 發(fā)現(xiàn)生成的視頻會瞬間播放完成后,意識到是時間戳的問題,檢查時間戳代碼:
while (av_read_frame(inputFormatContext, &packet) >= 0) {
if (packet.pts == AV_NOPTS_VALUE){
// 判斷pts是否為空
// 添加時間戳...
}
}
? ? ? ? 一開始沒注意到有B幀,想著把pts、dts設(shè)置相等,并且按照間隔遞增就可以了:
AVStream *inStream = inputFormatContext->streams[packet.stream_index];
AVStream *outStream = outputFormatContext->streams[packet.stream_index];
// 計算兩幀之間的間隔,以輸入流的時間基計算
int64_t frameDuration = av_rescale_q(1, av_inv_q(inStream->time_base), inStream->r_frame_rate);
// 轉(zhuǎn)換時間基為輸出流的時間基
int64_t _t = av_rescale_q(frameDuration, inStream->time_base, outStream->time_base);
// 當前幀的時間戳等于上一幀的時間戳加上兩幀之間的間隔
packet.pts = last_pts + _t;
// dts設(shè)置成一樣
packet.dts = packet.pts;
? ? ? ? 對于只有I和P幀的h264流,上述方法可以領(lǐng)流正常播放,但如果h264中有B幀,則會導致在某些播放器上播放異?!?strong>幀顯示順序錯誤,物體“顫抖”著移動。
? ? ? ? 對于含有B幀的視頻,其dts和pts應(yīng)該是按照這種順序進行的:
幀 | I | B | P | B | P | B | P |
dts | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
pts | 1 | 3 | 2 | 5 | 4 | 7 | 6 |
? ? ? ? 注:dts需要遞增,解碼需要按照順序解碼。任何一幀的pts應(yīng)大于等于dts,因為需要先解碼后才能顯示。因此上述需要調(diào)整:
幀 | I | B | P | B | P | B | P |
dts | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
pts | 2 | 4 | 3 | 6 | 5 | 8 | 7 |
? ? ? ? 為了使視頻打開時就有畫面能播放,因此需要將第一幀的pts調(diào)整為0,dts是可以為負數(shù)的,但需要保持遞增。調(diào)整后:
幀 | I | B | P | B | P | B | P |
dts | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
pts | 0 | 2 | 1 | 4 | 3 | 6 | 5 |
? ? ? ? 以上是最簡單的例子,也可以推導出變形:
幀 | I | B | B | P | B | B | P |
dts | -2 | -1 | 0 | 1 | 2 | 3 | 4 |
pts | 0 | 2 | 3 | 1 | 5 | 6 | 4 |
? ? ? ? 因此,時間戳的規(guī)律和我們的視頻中有多少連續(xù)的B幀有關(guān)。
? ? ? ? 以上討論的假設(shè)都是一個幀組中只有一個I幀的情況下,如果一個GOP中有多個I幀時情況會更復雜一點(沒做整理,本文暫不討論):
幀 | I(IDR) | B | P | B | P | B | I | B | P | B | P | I(IDR) | ... |
dts | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
pts | 1 | 3 | 2 | 5 | 4 | 7 | 6 | 9 | 8 | 11 | 10 | 12 |
? ? ? ??總結(jié):加時間戳的前提是要知道h264文件幀的類型結(jié)構(gòu)(是否有B幀、連續(xù)的B幀數(shù)量、一個GOP是否有多個I幀)
后記:
? ? ? ? 由于我的文件都是我自己生成的,幀結(jié)構(gòu)都是統(tǒng)一的,因此可以輕松的找出添加時間戳的規(guī)律。但對于不同幀結(jié)構(gòu)的原始流,找出統(tǒng)一規(guī)律還是比較麻煩,本文不再闡述。
? ? ? ? 附對于最簡單的含有B幀的處理代碼(IBPBPBPBP):
// 將多個h264文件合并成mp4文件
int mutexMp4File(){
// 創(chuàng)建輸出文件
// m_outputFile:輸出文件名 xxx.mp4
AVFormatContext* outputFormatContext = nullptr;
if (avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, m_outputFile.c_str()) < 0) {
return -1;
}
// 準備工作:遍歷輸入文件列表
// m_inputFiles:輸入文件名列表
for (const auto& inputFile : m_inputFiles) {
// 打開輸入文件
AVFormatContext* inputFormatContext = nullptr;
if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
return -1;
}
// 查找輸入文件的流信息
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return -1;
}
// 復制輸入文件的流到輸出文件
for (unsigned int i = 0; i < inputFormatContext->nb_streams; ++i) {
// 獲取輸入流
AVStream* inputStream = inputFormatContext->streams[i];
// 創(chuàng)建輸出流
AVStream* outputStream = avformat_new_stream(outputFormatContext, nullptr);
if (outputStream == nullptr) {
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return -1;
}
// 復制輸入流的編解碼器參數(shù)到輸出流
if (avcodec_parameters_copy(outputStream->codecpar, inputStream->codecpar) < 0) {
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return -1;
}
// 設(shè)置輸出流的編解碼器標志為"copy"
outputStream->codecpar->codec_tag = 0;
}
// 關(guān)閉輸入文件
avformat_close_input(&inputFormatContext);
}
// 打開輸出文件
AVIOContext* outputIOContext = nullptr;
if (avio_open(&outputFormatContext->pb, m_outputFile.c_str(), AVIO_FLAG_WRITE) < 0) {
avformat_free_context(outputFormatContext);
return -1;
}
// 寫入文件頭部
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
avformat_free_context(outputFormatContext);
return -1;
}
int64_t gdts_notime = 0;//拼接多個文件的時間戳
// 遍歷輸入文件列表,寫入數(shù)據(jù)到輸出文件
for (const auto& inputFile : m_inputFiles) {
// 打開輸入文件
AVFormatContext* inputFormatContext = nullptr;
if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
avformat_free_context(outputFormatContext);
return -1;
}
// 查找輸入文件的流信息
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return -1;
}
// 從輸入文件讀取數(shù)據(jù)并寫入輸出文件
AVPacket packet;
int64_t p_max_dts = 0;
int i = 0;
while (av_read_frame(inputFormatContext, &packet) >= 0) {
AVStream *inStream = inputFormatContext->streams[packet.stream_index];
AVStream *outStream = outputFormatContext->streams[packet.stream_index];
if (packet.pts == AV_NOPTS_VALUE){
// 起始 只能針對 IBPBPBP 連續(xù)B幀為1的流
int nalu_type = 0,startIndex = 0;
if (packet.data[0]==0x00 && packet.data[1]==0x00 && packet.data[2]==0x01){
nalu_type = 0x1f & packet.data[3];
startIndex = 3;
}else if(packet.data[0]==0x00 && packet.data[1]==0x00 && packet.data[2]==0x00 && packet.data[3]==0x01){
nalu_type = 0x1f & packet.data[4];
startIndex = 4;
}
int64_t frameDuration = av_rescale_q(1, av_inv_q(inStream->time_base), inStream->r_frame_rate);
int64_t _t = av_rescale_q(frameDuration, inStream->time_base, outStream->time_base);
p_max_dts = _t*(i+1);
packet.dts = p_max_dts + gdts_notime - _t;
int xt = (int)packet.data[startIndex+1] & 0xC0;
if (nalu_type == 0x05 || nalu_type == 0x06 || nalu_type == 0x07 || nalu_type == 0x08){ //I幀開頭
packet.pts = packet.dts + _t;
}else if(nalu_type == 0x01 && xt == 0x80){ //P幀
packet.pts = packet.dts;
}else if(nalu_type == 0x01 && xt == 0xC0){ //B幀
packet.pts = packet.dts + _t*2;
}else{
// Error!
packet.pts = packet.dts;
}
}else{
// Error!
}
// 將包的流索引設(shè)置為輸出流索引
packet.stream_index = inStream->index;
// 寫入輸出文件
av_interleaved_write_frame(outputFormatContext, &packet);
av_packet_unref(&packet);
i++;
}
gdts_notime += p_max_dts;
// 關(guān)閉輸入文件
avformat_close_input(&inputFormatContext);
}
// 寫入文件尾部
av_write_trailer(outputFormatContext);
// 關(guān)閉輸出文件
avio_close(outputFormatContext->pb);
// 釋放資源
avformat_free_context(outputFormatContext);
return 0;
}
? ? ? ? 代碼中的求幀類型的方法是旁門左道且有針對性的,可能不適合其他場景。
? ? ? ?文章來源:http://www.zghlxwxcb.cn/news/detail-811101.html
????????對于文中任何錯誤,歡迎指正。文章來源地址http://www.zghlxwxcb.cn/news/detail-811101.html
到了這里,關(guān)于[解決思路]關(guān)于h264裸流合成mp4時時間戳添加問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!