前言
在視頻編輯領(lǐng)域經(jīng)常會碰到這樣的需求場景,用戶選擇了多段感興趣的視頻想把它們拼接成一個文件。本文的目標(biāo)就是實現(xiàn)多段視頻文件的合并。同時也接觸到一個新的概念,流媒體封裝格式類型:
媒體格式分為流式和非流式,主要區(qū)別:
1、非流式的元信息通常存儲在文件中開頭,有時在結(jié)尾;
2、流式的元信息跟具體音視頻數(shù)據(jù)同步存放的,所以多個流式文件簡單串聯(lián)在一起形成新的文件也能正常播放;多個非流式文件的合并則可能需要重新編解碼才可以正常播放
3、例如mpg格式就是流式格式,通過直接依次取出每個文件的AVPacket,然后依次調(diào)用av_write_frame()即可實現(xiàn)文件合并
4、例如mp4格式就是非流式格式,如果采用上面的流程合并則要求各個文件具有相同的編碼方式,分辨率,像素格式等等才可以,否則就會失敗。因為非流式格式的元信息只能描述一種類型的音視頻數(shù)據(jù)
實現(xiàn)思路分析
對于視頻截取來說,被截取的部分的音視頻編碼方式以及碼流格式和源文件肯定是一樣的,而對于多個音視頻文件合并成一個音視頻文件,每一個要被合并的音視頻文件,其文件容器格式,音視頻編碼方式,音視頻碼流格式都有可能是不一樣的,而且還有可能出現(xiàn)某一個文件只有音頻或者視頻,情況比從一個視頻文件中截取一段復(fù)雜許多。這里由簡易難的逐步實現(xiàn)。
這里假設(shè)要合并的文件具有相同的容器格式
- 合并任意兩個相同容器類型及編碼參數(shù)的音視頻文件
這是最簡單的合并場景,編碼參數(shù)都相同就意味著視頻的分辨率,像素格式,編碼法方式以及音頻的采樣率,采樣格式,聲道類型等等參數(shù)都一樣,那么就不需要重新進行編解碼了。合并思路就是將每一個文件里面的AVPacket取出來重新封裝到新的文件中即可,但是需要注意兩點:
1、要合并的兩個文件的音/視頻碼流格式可能不一致,那么再封裝時添加音/視頻流編碼參數(shù)時要保持一致
2、第二個文件的AVPacket的pts,dts的值要加上第一個文件的時長
- 合并任意多個相同容器類型但不同編碼參數(shù)的音視頻文件
這種場景就相對復(fù)雜一點,不僅僅需要重新編解碼,對于不同文件不同分辨率,采樣率,采樣格式,像素格式的音/視頻還需要進行視頻轉(zhuǎn)換以及音頻轉(zhuǎn)換,所以實現(xiàn)思路如下:
1、首先取出所有文件中分辨率最小的作為最終合并文件的分辨率,第一個視頻文件的編碼方式,像素格式作為最終合并文件的編碼方式及像素格式
2、然后取出第一個音頻文件的編碼方式,采樣率,采樣格式作為最終合并文件的編碼參數(shù)
3、依次對每個文件的AVPacket進行重新解碼,然后根據(jù)根據(jù)編碼參數(shù)決定是否進行轉(zhuǎn)換,最后再編碼為新的AVPacket(同時要調(diào)整其pts和dts值)封裝到最終的文件中
流程圖
-
合并任意多個相同容器類型但不同編碼參數(shù)的音視頻文件流程圖
image.png -
合并任意多個相同容器類型但不同編碼參數(shù)的音視頻文件流程圖
image.png
實現(xiàn)代碼
這里將這兩個目標(biāo)的實現(xiàn)放在了同一個類中,對于資源的釋放采用了 一個函數(shù),故公共代碼如下:
void Merge::releasesources()
{
if (in_fmt1) {
avformat_close_input(&in_fmt1);
in_fmt1 = NULL;
}
if (in_fmt2) {
avformat_close_input(&in_fmt2);
in_fmt2 = NULL;
}
if (ou_fmt) {
avformat_free_context(ou_fmt);
ou_fmt = NULL;
}
for (int i=0; i<in_fmts.size(); i++) {
AVFormatContext *fmt = in_fmts[i];
avformat_close_input(&fmt);
}
for (int i = 0; i<de_ctxs.size(); i++) {
DeCtx ctx = de_ctxs[i];
if (ctx.video_de_ctx) {
avcodec_free_context(&ctx.video_de_ctx);
ctx.video_de_ctx = NULL;
}
if (ctx.audio_de_ctx) {
avcodec_free_context(&ctx.audio_de_ctx);
ctx.audio_de_ctx = NULL;
}
}
if (video_en_ctx) {
avcodec_free_context(&video_en_ctx);
video_en_ctx = NULL;
}
if (audio_en_ctx) {
avcodec_free_context(&audio_en_ctx);
audio_en_ctx = NULL;
}
if (swsctx) {
sws_freeContext(swsctx);
swsctx = NULL;
}
if (swrctx) {
swr_free(&swrctx);
swrctx = NULL;
}
if (video_de_frame) {
av_frame_free(&video_de_frame);
}
if (audio_de_frame) {
av_frame_free(&audio_de_frame);
}
if (video_en_frame) {
av_frame_free(&video_en_frame);
}
if (audio_en_frame) {
av_frame_free(&audio_en_frame);
}
if (audio_buffer) {
av_freep(&audio_buffer[0]);
}
}
- 合并任意多個相同容器類型但不同編碼參數(shù)的音視頻文件實現(xiàn)代碼
#include <stdio.h>
#include <string>
#include <vector>
#include <limits.h>
#include "CLog.h"
using namespace std;
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
class Merge
{
public:
Merge();
~Merge();
/** 媒體格式類型:
* 媒體格式分為流式和非流式。主要區(qū)別在于兩種文件格式如何嵌入元信息,非流式的元信息通常存儲在文件中開頭,有時在結(jié)尾;而流式的元信息跟
* 具體音視頻數(shù)據(jù)同步存放的。所以多個流式文件簡單串聯(lián)在一起形成新的文件也能正常播放。而多個非流式文件的合并則可能要重新編解碼才可以
*/
/** 流式媒體容器(MPG,FLV)的合并
* 實現(xiàn)合并任意兩個相同容器類型容器文件的功能;為了簡化處理過程,假設(shè)這兩個文件的音/視頻編碼方式都一樣,但是碼流有可能不一樣
* 目的:文件合并后文件能夠按照合并的順序正常播放
*/
void MergeTwo();
private:
string dstpath;
AVFormatContext *in_fmt1;
AVFormatContext *in_fmt2;
AVFormatContext *ou_fmt;
int video1_in_index,audio1_in_index;
int video2_in_index,audio2_in_index;
int video_ou_index,audio_ou_index;
int64_t next_video_pts,next_audio_pts;
// 釋放資源
releasesources();
};
void Merge::MergeTwo()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("2-video_audio_advanced");
if (pos == string::npos) {
LOGD("not find file");
return;
}
string srcDic = curFile.substr(0,pos) + "filesources/";
/** 媒體格式類型:
* 媒體格式分為流式和非流式。主要區(qū)別在于兩種文件格式如何嵌入元信息,非流式的元信息通常存儲在文件中開頭,有時在結(jié)尾;而流式的元信息跟
* 具體音視頻數(shù)據(jù)同步存放的。所以多個流式文件簡單串聯(lián)在一起形成新的文件也能正常播放。而多個非流式文件的合并則可能要重新編解碼才可以
* 如下mpg格式就是流式格式,通過直接依次取出每個文件的AVPacket,然后依次調(diào)用av_write_frame()即可實現(xiàn)文件合并
* 如下mp4格式就是非流式格式,如果采用上面的流程合并則要求各個文件具有相同的編碼方式,分辨率,像素格式等等才可以,否則就會失敗。因為非流式格式的元信息只能描述一種類型的音
* 視頻數(shù)據(jù)
*/
string srcPath1 = srcDic + "ll.mpg";
string srcPath2 = srcDic + "lr.mpg";
string dstPath = srcDic + "1-merge_1.mpg";
// string srcPath1 = srcDic + "test_1280x720_3.mp4";
// string srcPath2 = srcDic + "test_1280x720_5.mp4";
// string dstPath = srcDic + "1-merge_1.mp4";
// 打開要合并的源文件1
int ret = 0;
/** 遇到問題:解封裝mpg格式視頻編碼參數(shù)始終不正確,提示"[mp3float @ 0x104808800] Header missing"
* 分析原因:mpg格式對應(yīng)的demuxer為ff_mpegps_demuxer,它沒有.extensions字段(ff_mov_demuxer格式就有),所以最終它會靠read_probe對應(yīng)的
* 方法去分析格式,最終會調(diào)用到av_probe_input_format3()中去,該方法又會重新用每個解封裝器進行解析為ff_mpegvideo_demuxer,如果沒有將該接封裝器封裝進去則就會出問題
* 解決方案:要想解封裝mpg格式的視頻編碼參數(shù),必須要同時編譯ff_mpegps_demuxer和ff_mpegvideo_demuxer及ff_mpegvideo_parser,ff_mpeg1video_decoder,ff_mp2float_decoder
*/
if ((ret = avformat_open_input(&in_fmt1,srcPath1.c_str(),NULL,NULL)) < 0) {
LOGD("avformat_open_input in_fmt1 fail %s",av_err2str(ret));
return;
}
if (avformat_find_stream_info(in_fmt1, NULL) < 0) {
LOGD("avformat_find_stream_info infmt1 fail");
releasesources();
return;
}
// 將源文件1音視頻索引信息找出來
for (int i=0; i<in_fmt1->nb_streams; i++) {
AVStream *stream = in_fmt1->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video1_in_index == -1) {
video1_in_index = i;
}
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio1_in_index == -1) {
audio1_in_index = i;
}
}
// 打開要合并的源文件2
if (avformat_open_input(&in_fmt2,srcPath2.c_str(),NULL,NULL) < 0) {
LOGD("avformat_open_input in_fmt2 fail");
releasesources();
return;
}
if (avformat_find_stream_info(in_fmt2, NULL) < 0) {
LOGD("avformat_find_stream_info in_fmt2 fail");
releasesources();
return;
}
// 將源文件2音視頻索引信息找出來
for (int i=0; i<in_fmt2->nb_streams; i++) {
AVStream *stream = in_fmt2->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video2_in_index == -1) {
video2_in_index = i;
}
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio2_in_index == -1) {
audio2_in_index = i;
}
}
// 打開最終要封裝的文件
if (avformat_alloc_output_context2(&ou_fmt, NULL, NULL, dstPath.c_str()) < 0) {
LOGD("avformat_alloc_out_context2() fail");
releasesources();
return;
}
// 為封裝的文件添加視頻流信息;由于假設(shè)兩個文件的視頻流具有相同的編碼方式,這里就是簡單的流拷貝
for (int i=0;i<1;i++) {
if (video1_in_index != -1) {
AVStream *stream = avformat_new_stream(ou_fmt,NULL);
video_ou_index = stream->index;
// 由于是流拷貝方式
AVStream *srcstream = in_fmt1->streams[video1_in_index];
if (avcodec_parameters_copy(stream->codecpar, srcstream->codecpar) < 0) {
LOGD("avcodec_parameters_copy fail");
releasesources();
return;
}
// codec_id和code_tag共同決定了一種編碼方式在容器里面的碼流格式,所以如果源文件與目的文件的碼流格式不一致,那么就需要將目的文件
// 的code_tag 設(shè)置為0,當(dāng)調(diào)用avformat_write_header()函數(shù)后會自動將兩者保持一致
unsigned int src_tag = srcstream->codecpar->codec_tag;
if (av_codec_get_id(ou_fmt->oformat->codec_tag, src_tag) != stream->codecpar->codec_tag) {
stream->codecpar->codec_tag = 0;
}
break;
}
if (video2_in_index != -1) { // 只要任何一個文件有視頻流都創(chuàng)建視頻流
AVStream *stream = avformat_new_stream(ou_fmt,NULL);
video_ou_index = stream->index;
// 由于是流拷貝方式
AVStream *srcstream = in_fmt2->streams[video2_in_index];
if (avcodec_parameters_copy(stream->codecpar,srcstream->codecpar) < 0) {
LOGD("avcodec_parameters_copy 2 fail");
releasesources();
return;;
}
unsigned int src_tag = srcstream->codecpar->codec_tag;
if (av_codec_get_id(ou_fmt->oformat->codec_tag,src_tag) != stream->codecpar->codec_tag) {
stream->codecpar->codec_tag = 0;
}
}
}
// 為封裝的文件添加流信息;由于假設(shè)兩個文件的視頻流具有相同的編碼方式,這里就是簡單的流拷貝
for (int i=0;i<1;i++) {
if (audio1_in_index != -1) {
AVStream *dststream = avformat_new_stream(ou_fmt,NULL);
AVStream *srcstream = in_fmt1->streams[audio1_in_index];
if (avcodec_parameters_copy(dststream->codecpar,srcstream->codecpar) < 0) {
LOGD("avcodec_parameters_copy1 fail");
releasesources();
return;
}
audio_ou_index = dststream->index;
break;
}
if (audio2_in_index != -1) {
AVStream *dststream = avformat_new_stream(ou_fmt, NULL);
AVStream *srcstream = in_fmt2->streams[audio1_in_index];
if (avcodec_parameters_copy(dststream->codecpar,srcstream->codecpar) < 0) {
LOGD("avcodec_parameters_copy 1 fial");
releasesources();
return;
}
audio_ou_index = dststream->index;
}
}
// 打開輸出上下文
if (!(ou_fmt->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&ou_fmt->pb,dstPath.c_str(),AVIO_FLAG_WRITE) < 0) {
LOGD("avio_open fail");
releasesources();
return;
}
}
/** 遇到問題:寫入mpg容器時提示"mpeg1video files have exactly one stream"
* 分析原因:編譯mpg的封裝器錯了,之前寫的--enable-muxer=mpeg1video實際上應(yīng)該是--enable-muxer=mpeg1system
* 解決方案:編譯mpg的封裝器換成--enable-muxer=mpeg1system
*/
// 寫入文件頭
if (avformat_write_header(ou_fmt, NULL) < 0) {
LOGD("avformat_write_header fail");
releasesources();
return;
}
// 進行流拷貝;源文件1
AVPacket *in_pkt1 = av_packet_alloc();
while(av_read_frame(in_fmt1,in_pkt1) >=0) {
// 視頻流
if (in_pkt1->stream_index == video1_in_index) {
AVStream *srcstream = in_fmt1->streams[in_pkt1->stream_index];
AVStream *dststream = ou_fmt->streams[video_ou_index];
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
next_video_pts = max(in_pkt1->pts + in_pkt1->duration,next_video_pts);
av_packet_rescale_ts(in_pkt1, srcstream->time_base, dststream->time_base);
// 更正目標(biāo)流的索引
in_pkt1->stream_index = video_ou_index;
LOGD("pkt1 video %d(%6s) du %d(%s)",in_pkt1->pts,av_ts2timestr(in_pkt1->pts,&dststream->time_base),in_pkt1->duration,av_ts2timestr(in_pkt1->duration,&dststream->time_base));
}
// 音頻流
if (in_pkt1->stream_index == audio1_in_index) {
AVStream *srcstream = in_fmt1->streams[in_pkt1->stream_index];
AVStream *dststream = ou_fmt->streams[audio_ou_index];
next_audio_pts = max(in_pkt1->pts + in_pkt1->duration,next_audio_pts);
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
av_packet_rescale_ts(in_pkt1, srcstream->time_base, dststream->time_base);
// 更正目標(biāo)流的索引
in_pkt1->stream_index = audio_ou_index;
LOGD("pkt1 audio %d(%6s) du%d(%s)",in_pkt1->pts,av_ts2timestr(in_pkt1->pts,&dststream->time_base),in_pkt1->duration,av_ts2timestr(in_pkt1->duration,&dststream->time_base));
}
// 向封裝器中寫入數(shù)據(jù)
if((ret = av_write_frame(ou_fmt, in_pkt1)) < 0) {
LOGD("av_write_frame fail1 %s",av_err2str(ret));
releasesources();
return;;
}
}
// 進行流拷貝;源文件1
while(true) {
AVPacket *in_pkt2 = av_packet_alloc();
if(av_read_frame(in_fmt2,in_pkt2) <0 ) break;
/** 遇到問題:寫入數(shù)據(jù)是提示"[mp4 @ 0x10100ba00] Application provided invalid, non monotonically increasing dts to muxer in stream 1: 4046848 >= 0"
* 分析原因:因為是兩個源文件進行合并,對于每一個源文件來說,它的第一個AVPacket的pts都是0開始的
* 解決方案:所以第二個源文件的pts,dts,duration就應(yīng)該加上前面源文件的duration最大值
*/
// 視頻流
if (in_pkt2->stream_index == video2_in_index) {
AVStream *srcstream = in_fmt2->streams[in_pkt2->stream_index];
AVStream *dststream = ou_fmt->streams[video_ou_index];
if (next_video_pts > 0) {
AVStream *srcstream2 = in_fmt1->streams[video1_in_index];
in_pkt2->pts = av_rescale_q(in_pkt2->pts, srcstream->time_base, srcstream2->time_base) + next_video_pts;
in_pkt2->dts = av_rescale_q(in_pkt2->dts, srcstream->time_base, srcstream2->time_base) + next_video_pts;
in_pkt2->duration = av_rescale_q(in_pkt2->duration, srcstream->time_base, srcstream2->time_base);
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
av_packet_rescale_ts(in_pkt2, srcstream2->time_base, dststream->time_base);
} else {
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
av_packet_rescale_ts(in_pkt2, srcstream->time_base, dststream->time_base);
}
LOGD("pkt2 video %lld(%s)",in_pkt2->pts,av_ts2timestr(in_pkt2->pts,&dststream->time_base));
// 更正目標(biāo)流的索引
in_pkt2->stream_index = video_ou_index;
}
// 音頻流
if (in_pkt2->stream_index == audio2_in_index) {
if (in_pkt2->pts == AV_NOPTS_VALUE) {
in_pkt2->pts = in_pkt2->dts;
}
if (in_pkt2->dts == AV_NOPTS_VALUE) {
in_pkt2->dts = in_pkt2->pts;
}
AVStream *srcstream = in_fmt2->streams[in_pkt2->stream_index];
AVStream *dststream = ou_fmt->streams[audio_ou_index];
if (next_audio_pts > 0) {
AVStream *srcstream2 = in_fmt1->streams[audio1_in_index];
in_pkt2->pts = av_rescale_q(in_pkt2->pts, srcstream->time_base, srcstream2->time_base) + next_audio_pts;
in_pkt2->dts = av_rescale_q(in_pkt2->dts, srcstream->time_base, srcstream2->time_base) + next_audio_pts;
in_pkt2->duration = av_rescale_q(in_pkt2->duration, srcstream->time_base, srcstream2->time_base);
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
av_packet_rescale_ts(in_pkt2, srcstream2->time_base, dststream->time_base);
} else {
// 由于源文件和目的文件的時間基可能不一樣,所以這里要將時間戳進行轉(zhuǎn)換
av_packet_rescale_ts(in_pkt2, srcstream->time_base, dststream->time_base);
}
LOGD("pkt2 audio %d(%s)",in_pkt2->pts,av_ts2timestr(in_pkt2->pts,&dststream->time_base));
// 更正目標(biāo)流的索引
in_pkt2->stream_index = audio_ou_index;
}
// 向封裝器中寫入數(shù)據(jù)
if(av_write_frame(ou_fmt, in_pkt2) < 0) {
LOGD("av_write_frame 2 fail");
releasesources();
return;
}
av_packet_unref(in_pkt2);
}
// 寫入文件尾部信息
av_write_trailer(ou_fmt);
LOGD("文件寫入完畢...");
// 釋放資源
releasesources();
}
備注:
codec_id和code_tag共同決定了一種編碼方式在容器里面的碼流格式,所以如果源文件與目的文件的碼流格式不一致,那么就需要將目的文件的code_tag 設(shè)置為0,當(dāng)調(diào)用avformat_write_header()函數(shù)后會自動將兩者保持一致
- 合并任意多個相同容器類型但不同編碼參數(shù)的音視頻文件實現(xiàn)代碼
#include <stdio.h>
#include <string>
#include <vector>
#include <limits.h>
#include "CLog.h"
using namespace std;
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
class Merge
{
public:
Merge();
~Merge();
/** 非流式媒體容器(MP4)的合并
* 實現(xiàn)合并任意多個相同容器類型容器文件的功能;合并后的文件分辨率取最低的文件分辨率,像素格式及顏色范圍取第一文件的。編碼方式則都取第一個文件的
* 目的:文件合并后文件能夠按照合并的順序正常播放
*/
void MergeFiles();
private:
string dstpath;
AVFormatContext *ou_fmt;
int video_ou_index,audio_ou_index;
int64_t next_video_pts,next_audio_pts;
// 所有的輸入源
vector<string> srcPaths;
// 所有的輸入文件解封裝器
vector<AVFormatContext*> in_fmts;
typedef struct DeCtx {
AVCodecContext *video_de_ctx;
AVCodecContext *audio_de_ctx;
}DeCtx;
// 所有輸入文件對應(yīng)的音視頻解碼器;如果某個文件沒有視頻則對應(yīng)的為NULL
vector<DeCtx> de_ctxs;
AVCodecContext *video_en_ctx;
AVCodecContext *audio_en_ctx;
// 用于視頻轉(zhuǎn)換
SwsContext *swsctx;
// 用于音頻轉(zhuǎn)換
SwrContext *swrctx;
#define MediaIndexCmd(index,fi,vi,ai,wi,hi,sr,pf,sf,cl,vc,ac,vb,ab,fp) \
index.file_index = fi; \
index.video_index = vi; \
index.audio_index = ai; \
index.width = wi; \
index.height = hi; \
index.sample_rate = sr; \
index.pix_fmt = pf; \
index.smp_fmt = sf; \
index.ch_layout = cl; \
index.video_codecId = vc; \
index.audio_codecId = ac; \
index.video_bit_rate = vb; \
index.audio_bit_rate = ab; \
index.fps = fp;
// 所有的輸入源音視頻索引
typedef struct MediaIndex {
int file_index;
int video_index;
int audio_index;
int width,height;
int sample_rate;
enum AVPixelFormat pix_fmt;
enum AVSampleFormat smp_fmt;
int64_t ch_layout;
AVCodecID video_codecId;
AVCodecID audio_codecId;
int64_t video_bit_rate,audio_bit_rate;
int fps;
}MediaIndex;
vector<MediaIndex> in_indexes;
AVFrame *video_de_frame;
AVFrame *audio_de_frame;
AVFrame *video_en_frame;
AVFrame *audio_en_frame;
MediaIndex curIndex;
MediaIndex preIndex;
// 最終編碼參考的源文件
MediaIndex wantIndex;
uint8_t **audio_buffer;
int left_size;
bool audio_init;
bool openInputFile();
bool addStream();
void doDecode(AVPacket *pkt,bool isVideo);
void updateAVFrame();
AVFrame* get_audio_frame(enum AVSampleFormat smpfmt,int64_t ch_layout,int sample_rate,int nb_samples);
void doConvert(AVFrame**dst,AVFrame *src,bool isVideo);
void doEncode(AVFrame *frame,bool isVideo);
void releasesources();
};
// 合并非流式容器
void Merge::MergeFiles()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("2-video_audio_advanced");
if (pos == string::npos) {
LOGD("not find file");
return;
}
string srcDic = curFile.substr(0,pos) + "filesources/";
string srcPath1 = srcDic + "test_1280x720_1.mp4";
string srcPath2 = srcDic + "test_1280x720_3.mp4";
dstpath = srcDic + "1-merge_1.mp4";
srcPaths.push_back(srcPath1);
srcPaths.push_back(srcPath2);
if (!openInputFile()) {
return;
}
if (!addStream()) {
return;
}
// 創(chuàng)建AVFRame
updateAVFrame();
// 開始解碼和重新編碼
for (int i=0; i<in_fmts.size();i++) {
AVFormatContext *in_fmt = in_fmts[i];
curIndex = in_indexes.at(i);
// 讀取數(shù)據(jù)
while (true) {
AVPacket *pkt = av_packet_alloc();
if (av_read_frame(in_fmt, pkt) < 0) {
LOGD("沒有數(shù)據(jù)了 %d",i);
av_packet_unref(pkt);
break;
}
// 解碼
doDecode(pkt,pkt->stream_index == curIndex.video_index);
// 使用完畢后釋放AVPacket
av_packet_unref(pkt);
}
// 刷新解碼緩沖區(qū);同時刷新兩個
doDecode(NULL,true);
doDecode(NULL,false);
LOGD("finish file %d",i);
}
// 寫入文件尾部
if(av_write_trailer(ou_fmt) < 0) {
LOGD("av_write_trailer fail");
}
LOGD("結(jié)束寫入");
// 是否資源
releasesources();
}
bool Merge::openInputFile()
{
MediaIndexCmd(wantIndex,0,-1,-1,INT_MAX,INT_MAX,0,AV_PIX_FMT_NONE,AV_SAMPLE_FMT_NONE,0,AV_CODEC_ID_NONE,AV_CODEC_ID_NONE,0,0,0);
int ret = 0;
for (int i=0; i<srcPaths.size(); i++) {
string srcpath = srcPaths[i];
AVFormatContext *in_fmt = NULL;
if ((ret = avformat_open_input(&in_fmt, srcpath.c_str(), NULL, NULL)) < 0) {
LOGD("%d avformat_open_input fail %d",i,ret);
releasesources();
return false;
}
if ((ret = avformat_find_stream_info(in_fmt, NULL)) < 0) {
LOGD("%d avformat_find_stream_info fail %d",i,ret);
releasesources();
return false;
}
in_fmts.push_back(in_fmt);
// 遍歷出源文件的音視頻信息
MediaIndex index;
MediaIndexCmd(index,i,-1,-1,INT_MAX,INT_MAX,0,AV_PIX_FMT_NONE,AV_SAMPLE_FMT_NONE,0,AV_CODEC_ID_NONE,AV_CODEC_ID_NONE,0,0,0);
DeCtx dectx = {NULL,NULL};
for (int j=0; j<in_fmt->nb_streams; j++) {
AVStream *stream = in_fmt->streams[j];
// 創(chuàng)建解碼器
AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
AVCodecContext *ctx = avcodec_alloc_context3(codec);
if (ctx == NULL) {
LOGD("avcodec_alloc_context3 fail");
releasesources();
return false;
}
// 設(shè)置解碼參數(shù)
if((ret = avcodec_parameters_to_context(ctx, stream->codecpar)) < 0) {
LOGD("avcodec_parameters_to_context");
releasesources();
return false;
}
// 初始化解碼器
if ((ret = avcodec_open2(ctx, codec, NULL)) < 0) {
LOGD("avcodec_open2 fail %d",ret);
releasesources();
return false;
}
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // 視頻
index.video_index = j;
index.width = stream->codecpar->width;
index.height = stream->codecpar->height;
index.video_codecId = stream->codecpar->codec_id;
index.pix_fmt = (enum AVPixelFormat)stream->codecpar->format;
index.video_bit_rate = stream->codecpar->bit_rate;
index.fps = stream->r_frame_rate.num;
// 輸出文件的視頻編碼參數(shù)
if (stream->codecpar->width < wantIndex.width) { // 取出最小值
wantIndex.width = stream->codecpar->width;
wantIndex.height = stream->codecpar->height;
wantIndex.video_codecId = stream->codecpar->codec_id;
wantIndex.pix_fmt = (enum AVPixelFormat)stream->codecpar->format;
wantIndex.video_bit_rate = stream->codecpar->bit_rate;
wantIndex.fps = stream->r_frame_rate.num;
}
dectx.video_de_ctx = ctx;
}
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
index.audio_index = j;
index.sample_rate = stream->codecpar->sample_rate;
index.ch_layout = stream->codecpar->channel_layout;
index.audio_codecId = stream->codecpar->codec_id;
index.smp_fmt = (enum AVSampleFormat)stream->codecpar->format;
index.audio_bit_rate = stream->codecpar->bit_rate;
// 輸出文件的音頻編碼標(biāo)準(zhǔn)
if (wantIndex.audio_codecId == AV_CODEC_ID_NONE) { // 取第一個出現(xiàn)的值
wantIndex.audio_index = j;
wantIndex.sample_rate = stream->codecpar->sample_rate;
wantIndex.ch_layout = stream->codecpar->channel_layout;
wantIndex.audio_codecId = stream->codecpar->codec_id;
wantIndex.smp_fmt = (enum AVSampleFormat)stream->codecpar->format;
wantIndex.audio_bit_rate = stream->codecpar->bit_rate;
}
dectx.audio_de_ctx = ctx;
}
}
in_indexes.push_back(index);
de_ctxs.push_back(dectx);
}
return true;
}
static int select_sample_rate(AVCodec *codec,int rate)
{
int best_rate = 0;
int deft_rate = 44100;
bool surport = false;
const int* p = codec->supported_samplerates;
if (!p) {
return deft_rate;
}
while (*p) {
best_rate = *p;
if (*p == rate) {
surport = true;
break;
}
p++;
}
if (best_rate != rate && best_rate != 0 && best_rate != deft_rate) {
return deft_rate;
}
return best_rate;
}
bool Merge::addStream()
{
if (avformat_alloc_output_context2(&ou_fmt, NULL, NULL, dstpath.c_str()) < 0) {
LOGD("avformat_alloc_out_context2 fail");
releasesources();
return false;
}
if (wantIndex.video_codecId != AV_CODEC_ID_NONE) {
// 添加視頻流
AVStream *stream = avformat_new_stream(ou_fmt, NULL);
video_ou_index = stream->index;
AVCodec *codec = avcodec_find_encoder(wantIndex.video_codecId);
AVCodecContext *ctx = avcodec_alloc_context3(codec);
if (ctx == NULL) {
LOGD("find ctx fail");
releasesources();
return false;
}
// 設(shè)置編碼參數(shù)
ctx->pix_fmt = wantIndex.pix_fmt;
ctx->width = wantIndex.width;
ctx->height = wantIndex.height;
ctx->bit_rate = wantIndex.video_bit_rate;
ctx->time_base = (AVRational){1,wantIndex.fps*100};
ctx->framerate = (AVRational){wantIndex.fps,1};
stream->time_base = ctx->time_base;
if (ou_fmt->oformat->flags & AVFMT_GLOBALHEADER) {
ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
if (codec->id == AV_CODEC_ID_H264) {
ctx->flags2 |= AV_CODEC_FLAG2_LOCAL_HEADER;
}
// 初始化上下文
if (avcodec_open2(ctx, codec, NULL) < 0) {
LOGD("avcodec_open2 fail");
releasesources();
return false;
}
if (avcodec_parameters_from_context(stream->codecpar, ctx) < 0) {
LOGD("avcodec_parameters_from_context fail");
releasesources();
return false;
}
video_en_ctx = ctx;
}
if (wantIndex.audio_codecId != AV_CODEC_ID_NONE) {
AVStream *stream = avformat_new_stream(ou_fmt, NULL);
audio_ou_index = stream->index;
AVCodec *codec = avcodec_find_encoder(wantIndex.audio_codecId);
AVCodecContext *ctx = avcodec_alloc_context3(codec);
if (ctx == NULL) {
LOGD("avcodec alloc fail");
releasesources();
return false;
}
// 設(shè)置音頻編碼參數(shù)
ctx->sample_fmt = wantIndex.smp_fmt;
ctx->channel_layout = wantIndex.ch_layout;
int relt_sample_rate = select_sample_rate(codec, wantIndex.sample_rate);
if (relt_sample_rate == 0) {
LOGD("cannot surpot sample_rate");
releasesources();
return false;
}
ctx->sample_rate = relt_sample_rate;
ctx->bit_rate = wantIndex.audio_bit_rate;
ctx->time_base = (AVRational){1,ctx->sample_rate};
stream->time_base = ctx->time_base;
if (ou_fmt->oformat->flags & AVFMT_GLOBALHEADER) {
ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
// 初始化編碼器
if (avcodec_open2(ctx, codec, NULL) < 0) {
LOGD("aovcec_open2() fail");
releasesources();
return false;
}
if (avcodec_parameters_from_context(stream->codecpar, ctx) < 0) {
LOGD("avcodec_parameters_from_context fail");
releasesources();
return false;
}
audio_en_ctx = ctx;
}
// 打開輸入上下文及寫入頭信息
if (!(ou_fmt->oformat->flags & AVFMT_NOFILE)) {
if (avio_open2(&ou_fmt->pb, dstpath.c_str(), AVIO_FLAG_WRITE, NULL, NULL) < 0) {
LOGD("avio_open2 fail");
releasesources();
return false;
}
}
if (avformat_write_header(ou_fmt, NULL) < 0) {
LOGD("avformat_write_header fail");
releasesources();
return false;
}
return true;
}
void Merge::updateAVFrame()
{
if (video_en_frame) {
av_frame_unref(video_en_frame);
}
if (audio_en_frame) {
av_frame_unref(audio_en_frame);
}
if (video_en_ctx) {
video_en_frame = av_frame_alloc();
video_de_frame = av_frame_alloc();
video_en_frame->format = video_en_ctx->pix_fmt;
video_en_frame->width = video_en_ctx->width;
video_en_frame->height = video_en_ctx->height;
av_frame_get_buffer(video_en_frame, 0);
av_frame_make_writable(video_en_frame);
}
if (audio_en_ctx) {
audio_en_frame = av_frame_alloc();
audio_de_frame = av_frame_alloc();
audio_en_frame->format = audio_en_ctx->sample_fmt;
audio_en_frame->channel_layout = audio_en_ctx->channel_layout;
audio_en_frame->sample_rate = audio_en_ctx->sample_rate;
audio_en_frame->nb_samples = audio_en_ctx->frame_size;
av_frame_get_buffer(audio_en_frame, 0);
av_frame_make_writable(audio_en_frame);
}
}
void Merge::doDecode(AVPacket *pkt,bool isVideo)
{
DeCtx dectx = de_ctxs[curIndex.file_index];
AVCodecContext *ctx = NULL;
AVFrame *de_frame = NULL;
AVFrame *en_frame = NULL;
if (isVideo) { // 說明是視頻
ctx = dectx.video_de_ctx;
de_frame = video_de_frame;
en_frame = video_en_frame;
} else { // 說明是音頻
ctx = dectx.audio_de_ctx;
de_frame = audio_de_frame;
en_frame = audio_en_frame;
isVideo = false;
}
if (ctx == NULL) {
LOGD("%d error avcodecxtx",curIndex);
releasesources();
return;
}
// 開始解碼
int ret = 0;
avcodec_send_packet(ctx, pkt);
while (true) {
ret = avcodec_receive_frame(ctx, de_frame);
if (ret == AVERROR_EOF && curIndex.file_index + 1 == in_fmts.size()) { // 說明解碼完畢
doEncode(NULL,ctx->width > 0);
LOGD("decode finish");
break;
} else if(ret < 0) {
break;
}
// 解碼成功;開始編碼
doConvert(&en_frame, de_frame,de_frame->width > 0);
}
}
void Merge::doConvert(AVFrame **dst, AVFrame *src,bool isVideo)
{
int ret = 0;
if (isVideo && (src->width != (*dst)->width || src->format != (*dst)->format)) {
MediaIndex index = in_indexes[curIndex.file_index];
if (!swsctx) {
swsctx = sws_getContext(index.width, index.height, index.pix_fmt,
video_en_ctx->width, video_en_ctx->height, video_en_ctx->pix_fmt,
0, NULL, NULL, NULL);
if (swsctx == NULL) {
LOGD("sws_getContext fail");
releasesources();
return;
}
}
if((ret = sws_scale(swsctx, src->data, src->linesize, 0, src->height, (*dst)->data, (*dst)->linesize)) < 0) {
LOGD("sws_scale fail");
releasesources();
return;
}
/** 遇到問題:合成后的視頻時長不是各個視頻文件視頻時長之和
* 分析原因:因為每個視頻文件的fps不一樣,合并時pts沒有和每個視頻文件的fps對應(yīng)
* 解決方案:合并時pts要和每個視頻文件的fps對應(yīng)
*/
(*dst)->pts = next_video_pts + video_en_ctx->time_base.den/curIndex.fps;
next_video_pts += video_en_ctx->time_base.den/curIndex.fps;
doEncode((*dst),(*dst)->width > 0);
} else if (isVideo && (src->width == (*dst)->width && src->format == (*dst)->format)) {
av_frame_copy((*dst), src);
(*dst)->pts = next_video_pts + video_en_ctx->time_base.den/curIndex.fps;
next_video_pts += video_en_ctx->time_base.den/curIndex.fps;
doEncode((*dst),(*dst)->width > 0);
}
if (!isVideo && (src->sample_rate != (*dst)->sample_rate || src->format != (*dst)->format || src->channel_layout != (*dst)->channel_layout)) {
if (!swrctx) {
swrctx = swr_alloc_set_opts(NULL, audio_en_ctx->channel_layout, audio_en_ctx->sample_fmt, audio_en_ctx->sample_rate,
src->channel_layout, (enum AVSampleFormat)src->format, src->sample_rate, 0, NULL);
if ((ret = swr_init(swrctx)) < 0) {
LOGD("swr_alloc_set_opts() fail %d",ret);
releasesources();
return;
}
}
int dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swrctx, src->sample_rate)+src->nb_samples, (*dst)->sample_rate, src->sample_rate, AV_ROUND_UP);
if (dst_nb_samples != (*dst)->nb_samples) {
// 進行轉(zhuǎn)化
/** 遇到問題:音頻合并后時長并不是兩個文件之和
* 分析原因:當(dāng)進行音頻采樣率重采樣時,如采樣率升采樣,那么swr_convert()內(nèi)部會進行插值運算,這樣相對于原先的nb_samples會多出一些,所以swr_convert()可能需要多次
* 調(diào)用才可以將所有數(shù)據(jù)取完,那么這就需要建立一個緩沖區(qū)來自己組裝新采樣率下的AVFrame中的音頻數(shù)據(jù)
* 解決方案:建立音頻緩沖區(qū),依次組裝所有的音頻數(shù)據(jù)并且和pts對應(yīng)上
*/
if (!audio_init) {
ret = av_samples_alloc_array_and_samples(&audio_buffer, (*dst)->linesize, (*dst)->channels, (*dst)->nb_samples, (enum AVSampleFormat)(*dst)->format, 0);
audio_init = true;
left_size = 0;
}
bool first = true;
while (true) {
// 進行轉(zhuǎn)換
if (first) {
ret = swr_convert(swrctx, audio_buffer, (*dst)->nb_samples, (const uint8_t**)audio_de_frame->data, audio_de_frame->nb_samples);
if (ret < 0) {
LOGD("swr_convert() fail %d",ret);
releasesources();
return;
}
first = false;
int use = ret-left_size >= 0 ?ret - left_size:ret;
int size = av_get_bytes_per_sample((enum AVSampleFormat)audio_en_frame->format);
for (int ch=0; ch<audio_en_frame->channels; ch++) {
for (int i = 0; i<use; i++) {
(*dst)->data[ch][(i+left_size)*size] = audio_buffer[ch][i*size];
(*dst)->data[ch][(i+left_size)*size+1] = audio_buffer[ch][i*size+1];
(*dst)->data[ch][(i+left_size)*size+2] = audio_buffer[ch][i*size+2];
(*dst)->data[ch][(i+left_size)*size+3] = audio_buffer[ch][i*size+3];
}
}
// 編碼
left_size += ret;
if (left_size >= (*dst)->nb_samples) {
left_size -= (*dst)->nb_samples;
// 編碼
(*dst)->pts = av_rescale_q(next_audio_pts, (AVRational){1,(*dst)->sample_rate}, audio_en_ctx->time_base);
next_audio_pts += (*dst)->nb_samples;
doEncode((*dst),(*dst)->width > 0);
if (left_size > 0) {
int size = av_get_bytes_per_sample((enum AVSampleFormat)audio_en_frame->format);
for (int ch=0; ch<(*dst)->channels; ch++) {
for (int i = 0; i<left_size; i++) {
(*dst)->data[ch][i*size] = audio_buffer[ch][(use+i)*size];
(*dst)->data[ch][i*size+1] = audio_buffer[ch][(use+i)*size+1];
(*dst)->data[ch][i*size+2] = audio_buffer[ch][(use+i)*size+2];
(*dst)->data[ch][i*size+3] = audio_buffer[ch][(use+i)*size+3];
}
}
}
}
} else {
ret = swr_convert(swrctx, audio_buffer, (*dst)->nb_samples, NULL, 0);
if (ret < 0) {
LOGD("1 swr_convert() fail %d",ret);
releasesources();
return;
}
int size = av_get_bytes_per_sample((enum AVSampleFormat)audio_en_frame->format);
for (int ch=0; ch<audio_en_frame->channels; ch++) {
for (int i = 0; i < ret && i+left_size < audio_en_frame->nb_samples; i++) {
(*dst)->data[ch][(left_size + i)*size] = audio_buffer[ch][i*size];
(*dst)->data[ch][(left_size + i)*size + 1] = audio_buffer[ch][i*size + 1];
(*dst)->data[ch][(left_size + i)*size + 2] = audio_buffer[ch][i*size + 2];
(*dst)->data[ch][(left_size + i)*size + 3] = audio_buffer[ch][i*size + 3];
}
}
left_size += ret;
if (left_size >= (*dst)->nb_samples) {
left_size -= (*dst)->nb_samples;
LOGD("多了一個編碼");
// 編碼
(*dst)->pts = av_rescale_q(next_audio_pts, (AVRational){1,(*dst)->sample_rate}, audio_en_ctx->time_base);
next_audio_pts += audio_en_frame->nb_samples;
doEncode((*dst),(*dst)->width > 0);
} else {
break;
}
}
}
}
} else if (!isVideo && (src->nb_samples == (*dst)->nb_samples || src->sample_rate == (*dst)->sample_rate || src->format == (*dst)->format || src->channel_layout == (*dst)->channel_layout)) {
av_frame_copy((*dst), src);
/** 遇到問題:合并后音頻和視頻不同步
* 分析原因:因為每個音頻文件的采樣率,而合并是按照第一個文件的采樣率作為time_base的,沒有轉(zhuǎn)換過來
* 解決方案:合并時pts要和每個合并前音頻的采樣率對應(yīng)上
*/
src->pts = next_audio_pts + audio_en_ctx->time_base.den/curIndex.sample_rate * src->nb_samples;
next_audio_pts += audio_en_ctx->time_base.den/curIndex.sample_rate * src->nb_samples;
doEncode(src,src->width > 0);
}
}
AVFrame* Merge::get_audio_frame(enum AVSampleFormat smpfmt,int64_t ch_layout,int sample_rate,int nb_samples)
{
AVFrame * audio_en_frame = av_frame_alloc();
// 根據(jù)采樣格式,采樣率,聲道類型以及采樣數(shù)分配一個AVFrame
audio_en_frame->sample_rate = sample_rate;
audio_en_frame->format = smpfmt;
audio_en_frame->channel_layout = ch_layout;
audio_en_frame->nb_samples = nb_samples;
int ret = 0;
if ((ret = av_frame_get_buffer(audio_en_frame, 0)) < 0) {
LOGD("audio get frame buffer fail %d",ret);
return NULL;
}
if ((ret = av_frame_make_writable(audio_en_frame)) < 0) {
LOGD("audio av_frame_make_writable fail %d",ret);
return NULL;
}
return audio_en_frame;
}
void Merge::doEncode(AVFrame *frame,bool isVideo)
{
AVCodecContext *ctx = NULL;
if (isVideo) { // 說明是視頻
ctx = video_en_ctx;
} else {
ctx = audio_en_ctx;
}
int ret = avcodec_send_frame(ctx, frame);
while (true) {
AVPacket *pkt = av_packet_alloc();
ret = avcodec_receive_packet(ctx, pkt);
if (ret < 0) {
// LOGD("ret %s",av_err2str(ret));
av_packet_unref(pkt);
break;
}
// 編碼成功;寫入文件,寫入之前改變一下pts,dts和duration(因為此時pkt的pts是基于AVCodecContext的,要轉(zhuǎn)換成基于AVFormatContext的)
int index = ctx->width > 0?video_ou_index:audio_ou_index;
AVStream *stream = ou_fmt->streams[index];
av_packet_rescale_ts(pkt, ctx->time_base, stream->time_base);
pkt->stream_index = index;
LOGD("%s pts %d(%s)",pkt->stream_index == video_ou_index ? "video":"audio",pkt->pts,av_ts2timestr(pkt->pts,&stream->time_base));
if ((ret = av_write_frame(ou_fmt, pkt)) < 0) {
LOGD("%d av_write_frame fail %d",ret);
releasesources();
return;
}
av_packet_unref(pkt);
}
}
void Merge::addMusic()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("2-video_audio_advanced");
if (pos == string::npos) {
LOGD("not found fil");
return;
}
/** 遇到問題:如果添加的音頻文件為Mp3文件,則使用蘋果的內(nèi)置播放器及蘋果手機沒有聲音。如果添加的是aac音頻 則沒有問題
* 分析原因:暫時未知
* 解決方案:暫時未知
*/
string srcDic = curFile.substr(0,pos) + "filesources/";
string srcpath = srcDic + "test_1280x720_4.mp4";
// string srcpath2 = srcDic + "test-mp3-1.mp3";
string srcpath2 = srcDic + "test_441_f32le_2.aac";
string dstpath = srcDic + "11_add_music.mp4";
string start = "00:00:05";
start_pos += stoi(start.substr(0,2))*3600;
start_pos += stoi(start.substr(3,2))*60;
start_pos += stoi(start.substr(6,2));
in_fmt1 = NULL;// 用于解封裝視頻
in_fmt2 = NULL;// 用于解封裝音頻
ou_fmt = NULL; //用于封裝音視頻
// 打開視頻文件
if (avformat_open_input(&in_fmt1, srcpath.c_str(), NULL, NULL) < 0) {
LOGD("1 avformat_open_input() fail");
return;
}
if (avformat_find_stream_info(in_fmt1, NULL) < 0) {
LOGD("1 avformat_find_stream_info");
releasesources();
return;
}
// 打開音頻文件
if (avformat_open_input(&in_fmt2, srcpath2.c_str(), NULL, NULL) < 0) {
LOGD("2 avformat_open_input() fail");
return;
}
if (avformat_find_stream_info(in_fmt2, NULL) < 0) {
LOGD("2 avformat_find_stream_info");
releasesources();
return;
}
for (int i=0; i<in_fmt1->nb_streams; i++) {
AVStream *stream = in_fmt1->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video1_in_index = i;
break;
}
}
for (int i = 0; i<in_fmt2->nb_streams; i++) {
AVStream *stream = in_fmt2->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio1_in_index = i;
break;
}
}
// 打開輸出文件的封裝器
if (avformat_alloc_output_context2(&ou_fmt, NULL, NULL, dstpath.c_str()) < 0) {
LOGD("avformat_alloc_out_context2 ()");
releasesources();
return;
}
// 添加視頻流并從輸入源拷貝視頻流編碼參數(shù)
AVStream *stream = avformat_new_stream(ou_fmt, NULL);
video_ou_index = stream->index;
if (avcodec_parameters_copy(stream->codecpar, in_fmt1->streams[video1_in_index]->codecpar) < 0) {
releasesources();
return;
}
// 如果源和目標(biāo)文件的碼流格式不一致,則將目標(biāo)文件的code_tag賦值為0
if (av_codec_get_id(ou_fmt->oformat->codec_tag, in_fmt1->streams[video1_in_index]->codecpar->codec_tag) != stream->codecpar->codec_id) {
stream->codecpar->codec_tag = 0;
}
// 添加音頻流并從輸入源拷貝編碼參數(shù)
AVStream *a_stream = avformat_new_stream(ou_fmt, NULL);
audio_ou_index = a_stream->index;
if (avcodec_parameters_copy(a_stream->codecpar, in_fmt2->streams[audio1_in_index]->codecpar) < 0) {
LOGD("avcodec_parameters_copy fail");
releasesources();
return;
}
if (av_codec_get_id(ou_fmt->oformat->codec_tag, in_fmt2->streams[audio1_in_index]->codecpar->codec_tag) != a_stream->codecpar->codec_id) {
a_stream->codecpar->codec_tag = 0;
}
// 打開輸出上下文
if (!(ou_fmt->oformat->flags & AVFMT_NOFILE)) {
if (avio_open2(&ou_fmt->pb, dstpath.c_str(), AVIO_FLAG_WRITE, NULL, NULL) < 0) {
LOGD("avio_open2() fail");
releasesources();
return;
}
}
// 寫入頭文件
if (avformat_write_header(ou_fmt, NULL) < 0) {
LOGD("avformat_write_header()");
releasesources();
return;
}
AVPacket *v_pkt = av_packet_alloc();
AVPacket *a_pkt = av_packet_alloc();
// 寫入視頻
int64_t video_max_pts = 0;
while (av_read_frame(in_fmt1, v_pkt) >= 0) {
if (v_pkt->stream_index == video1_in_index) { // 說明是視頻
// 因為源文件和目的文件時間基可能不一致,所以這里要進行轉(zhuǎn)換
av_packet_rescale_ts(v_pkt, in_fmt1->streams[video1_in_index]->time_base, ou_fmt->streams[video_ou_index]->time_base);
v_pkt->stream_index = video_ou_index;
video_max_pts = max(v_pkt->pts, video_max_pts);
LOGD("video pts %d(%s)",v_pkt->pts,av_ts2timestr(v_pkt->pts, &stream->time_base));
if (av_write_frame(ou_fmt, v_pkt) < 0) {
LOGD("1 av_write_frame < 0");
releasesources();
return;
}
}
}
int64_t start_pts = start_pos * a_stream->time_base.den;
video_max_pts = av_rescale_q(video_max_pts, stream->time_base, a_stream->time_base);
// 寫入音頻
while (av_read_frame(in_fmt2, a_pkt) >= 0) {
if (a_pkt->stream_index == audio1_in_index) { // 音頻
// 源文件和目標(biāo)文件的時間基可能不一致,需要轉(zhuǎn)化
av_packet_rescale_ts(a_pkt, in_fmt2->streams[audio1_in_index]->time_base, ou_fmt->streams[audio_ou_index]->time_base);
// 保證以視頻時間軸的指定時間進行添加,那么實際上就是改變pts及dts的值即可
a_pkt->pts += start_pts;
a_pkt->dts += start_pts;
a_pkt->stream_index = audio_ou_index;
LOGD("audio pts %d(%s)",a_pkt->pts,av_ts2timestr(a_pkt->pts, &a_stream->time_base));
// 加入音頻的時長不能超過視頻的總時長
if (a_pkt->pts >= video_max_pts) {
break;
}
if (av_write_frame(ou_fmt, a_pkt) < 0) {
LOGD("2 av_write_frame < 0");
releasesources();
return;
}
}
}
av_write_trailer(ou_fmt);
LOGD("寫入完畢");
// 釋放資源
releasesources();
}
遇到問題
1、解封裝mpg格式視頻編碼參數(shù)始終不正確,提示"[mp3float @ 0x104808800] Header missing"
分析原因:mpg格式對應(yīng)的demuxer為ff_mpegps_demuxer,它沒有.extensions字段(ff_mov_demuxer格式就有),所以最終它會靠read_probe對應(yīng)的方法去分析格式,最終會調(diào)用到av_probe_input_format3()中去,該方法又會重新用每個解封裝器進行解析為ff_mpegvideo_demuxer,如果沒有將該接封裝器封裝進去則就會出問題
解決方案:要想解封裝mpg格式的視頻編碼參數(shù),必須要同時編譯ff_mpegps_demuxer和ff_mpegvideo_demuxer及ff_mpegvideo_parser,ff_mpeg1video_decoder,ff_mp2float_decoder
2、寫入mpg容器時提示"mpeg1video files have exactly one stream"
分析原因:編譯mpg的封裝器錯了,之前寫的--enable-muxer=mpeg1video實際上應(yīng)該是--enable-muxer=mpeg1system
解決方案:編譯mpg的封裝器換成--enable-muxer=mpeg1system
3、寫入數(shù)據(jù)是提示"[mp4 @ 0x10100ba00] Application provided invalid, non monotonically increasing dts to muxer in stream 1: 4046848 >= 0"
分析原因:因為是兩個源文件進行合并,對于每一個源文件來說,它的第一個AVPacket的pts都是0開始的
解決方案:所以第二個源文件的pts,dts,duration就應(yīng)該加上前面源文件的duration最大值
4、合成后的視頻時長不是各個視頻文件視頻時長之和
分析原因:因為每個視頻文件的fps不一樣,合并時pts沒有和每個視頻文件的fps對應(yīng)
解決方案:合并時pts要和每個視頻文件的fps對應(yīng)
5、音頻合并后時長并不是兩個文件之和
分析原因:當(dāng)進行音頻采樣率重采樣時,如采樣率升采樣,那么swr_convert()內(nèi)部會進行插值運算,這樣相對于原先的nb_samples會多出一些,所以swr_convert()可能需要多次調(diào)用才可以將所有數(shù)據(jù)取完,那么這就需要建立一個緩沖區(qū)來自己組裝新采樣率下的AVFrame中的音頻數(shù)據(jù)
解決方案:建立音頻緩沖區(qū),依次組裝所有的音頻數(shù)據(jù)并且和pts對應(yīng)上
6、合并后音頻和視頻不同步
分析原因:因為每個音頻文件的采樣率,而合并是按照第一個文件的采樣率作為time_base的,沒有轉(zhuǎn)換過來
解決方案:合并時pts要和每個合并前音頻的采樣率對應(yīng)上
音視頻多個文件合并為一個文件遇到問題還是挺多的,都一一解決,具體參考代碼注釋
項目地址
https://github.com/nldzsz/ffmpeg-demo
位于cppsrc目錄下的Merge.hpp/Merge.cpp文件中
項目下示例可運行于iOS/android/mac平臺,工程分別位于demo-ios/demo-android/demo-mac三個目錄下,可根據(jù)需要選擇不同平臺

