FFmpeg音頻播放器(1)-簡(jiǎn)介
FFmpeg音頻播放器(2)-編譯動(dòng)態(tài)庫(kù)
FFmpeg音頻播放器(3)-將FFmpeg加入到Android中
FFmpeg音頻播放器(4)-將mp3解碼成pcm
FFmpeg音頻播放器(5)-單輸入filter(volume,atempo)
FFmpeg音頻播放器(6)-多輸入filter(amix)
FFmpeg音頻播放器(7)-使用OpenSLES播放音頻
FFmpeg音頻播放器(8)-創(chuàng)建FFmpeg播放器
FFmpeg音頻播放器(9)-播放控制
FFmpeg使用過(guò)濾器filter的另外一個(gè)場(chǎng)景就是處理多個(gè)輸入數(shù)據(jù),比如視頻添加水印忿等,添加字幕糟需,音視頻合并等鳞滨。這類場(chǎng)景需要兩個(gè)及以上輸入端奉瘤。本節(jié)講amix亲族,它可以將多個(gè)音頻混音种呐。
處理流程
輸入AVFrame1 -> abuffer
-> amix -> aformat -> abuffersink -> 輸出AVFrame
輸入AVFrame2 -> abuffer
處理流程和單輸入過(guò)濾器大致相同狠半,只不過(guò)接收了多個(gè)輸入端噩死。因此需要多個(gè)filter上下文作為輸入端。
初始化amix
//初始化amix filter
int init_amix_filter(AVFilterGraph **pGraph, AVFilterContext **srcs, AVFilterContext **pOut,
jsize len) {
AVFilterGraph *graph = avfilter_graph_alloc();
for (int i = 0; i < len; i++) {
AVFilter *filter = avfilter_get_by_name("abuffer");
char name[50];
snprintf(name, sizeof(name), "src%d", i);
AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, filter, name);
if (avfilter_init_str(abuffer_ctx,
"sample_rate=48000:sample_fmt=s16p:channel_layout=stereo") < 0) {
LOGE("error init abuffer filter");
return -1;
}
srcs[i] = abuffer_ctx;
}
AVFilter *amix = avfilter_get_by_name("amix");
AVFilterContext *amix_ctx = avfilter_graph_alloc_filter(graph, amix, "amix");
char args[128];
snprintf(args, sizeof(args), "inputs=%d:duration=first:dropout_transition=3", len);
if (avfilter_init_str(amix_ctx, args) < 0) {
LOGE("error init amix filter");
return -1;
}
AVFilter *aformat = avfilter_get_by_name("aformat");
AVFilterContext *aformat_ctx = avfilter_graph_alloc_filter(graph, aformat, "aformat");
if (avfilter_init_str(aformat_ctx,
"sample_rates=48000:sample_fmts=s16p:channel_layouts=stereo") < 0) {
LOGE("error init aformat filter");
return -1;
}
AVFilter *sink = avfilter_get_by_name("abuffersink");
AVFilterContext *sink_ctx = avfilter_graph_alloc_filter(graph, sink, "sink");
avfilter_init_str(sink_ctx, NULL);
for (int i = 0; i < len; i++) {
if (avfilter_link(srcs[i], 0, amix_ctx, i) < 0) {
LOGE("error link to amix");
return -1;
}
}
if (avfilter_link(amix_ctx, 0, aformat_ctx, 0) < 0) {
LOGE("error link to aformat");
return -1;
}
if (avfilter_link(aformat_ctx, 0, sink_ctx, 0) < 0) {
LOGE("error link to sink");
return -1;
}
if (avfilter_graph_config(graph, NULL) < 0) {
LOGE("error config graph");
return -1;
}
*pGraph = graph;
*pOut = sink_ctx;
return 0;
}
這里用一個(gè)數(shù)組保存輸入AVFilterContex神年,通過(guò)遍歷循環(huán)將每個(gè)輸入端鏈接到amix過(guò)濾器已维,這樣就可以接收多個(gè)輸入端了。
使用amix
為了能夠傳入多個(gè)音頻數(shù)據(jù)已日,我們需要同時(shí)解碼多個(gè)音頻文件垛耳,因此在Java層,傳入字符串?dāng)?shù)組飘千。
external fun mixAudio(arr: Array<String>,out:String)
val path = "${Environment.getExternalStorageDirectory()}/test"
val paths = arrayOf(
"$path/a.mp3",
"$path/b.mp3",
"$path/c.mp3",
"$path/d.mp3"
)
mixAudio(paths,"$path/mix.pcm")
在jni層使用多個(gè)解碼器解碼每個(gè)文件
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_MainActivity_mixAudio(
JNIEnv *env,
jobject /* this */, jobjectArray _srcs, jstring _out) {
//將java傳入的字符串?dāng)?shù)組轉(zhuǎn)為c字符串?dāng)?shù)組
jsize len = env->GetArrayLength(_srcs);
const char *out_path = env->GetStringUTFChars(_out, 0);
char **pathArr = (char **) malloc(len * sizeof(char *));
int i = 0;
for (i = 0; i < len; i++) {
jstring str = static_cast<jstring>(env->GetObjectArrayElement(_srcs, i));
pathArr[i] = const_cast<char *>(env->GetStringUTFChars(str, 0));
}
//初始化解碼器數(shù)組
av_register_all();
AVFormatContext **fmt_ctx_arr = (AVFormatContext **) malloc(len * sizeof(AVFormatContext *));
AVCodecContext **codec_ctx_arr = (AVCodecContext **) malloc(len * sizeof(AVCodecContext *));
int stream_index_arr[len];
for (int n = 0; n < len; n++) {
AVFormatContext *fmt_ctx = avformat_alloc_context();
fmt_ctx_arr[n] = fmt_ctx;
const char *path = pathArr[n];
if (avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) {//打開(kāi)文件
LOGE("could not open file:%s", path);
return;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {//讀取音頻格式文件信息
LOGE("find stream info error");
return;
}
//獲取音頻索引
int audio_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_index = i;
LOGI("find audio stream index");
break;
}
}
if (audio_stream_index < 0) {
LOGE("error find stream index");
break;
}
stream_index_arr[n] = audio_stream_index;
//獲取解碼器
AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
codec_ctx_arr[n] = codec_ctx;
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
//打開(kāi)解碼器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("could not open codec");
return;
}
}
//初始化SwrContext
SwrContext *swr_ctx = swr_alloc();
enum AVSampleFormat in_sample_fmt = codec_ctx_arr[0]->sample_fmt;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
int in_sample_rate = codec_ctx_arr[0]->sample_rate;
int out_sample_rate = in_sample_rate;
uint64_t in_ch_layout = codec_ctx_arr[0]->channel_layout;
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
swr_alloc_set_opts(swr_ctx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout,
in_sample_fmt,
in_sample_rate, 0, NULL);
swr_init(swr_ctx);
int out_ch_layout_nb = av_get_channel_layout_nb_channels(out_ch_layout);//聲道個(gè)數(shù)
uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_SIZE);//重采樣數(shù)據(jù)
//初始化amix filter
AVFilterGraph *graph;
AVFilterContext **srcs = (AVFilterContext **) malloc(len * sizeof(AVFilterContext *));
AVFilterContext *sink;
avfilter_register_all();
init_amix_filter(&graph, srcs, &sink, len);
//開(kāi)始解碼
FILE *out_file = fopen(out_path, "wb");
AVFrame *frame = av_frame_alloc();
AVPacket *packet = av_packet_alloc();
int ret = 0, got_frame;
int index = 0;
while (1) {
for (int i = 0; i < len; i++) {
ret = av_read_frame(fmt_ctx_arr[i], packet);
if (ret < 0)break;
if (packet->stream_index == stream_index_arr[i]) {
ret = avcodec_decode_audio4(codec_ctx_arr[i], frame, &got_frame, packet);
if (ret < 0)break;
if (got_frame > 0) {
ret = av_buffersrc_add_frame(srcs[i], frame);//將解碼后的AVFrame加入到amix輸入端
if (ret < 0) {
LOGE("error add frame:%d", index);
break;
}
}
}
}
while (av_buffersink_get_frame(sink, frame) >= 0) {
swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **) frame->data,
frame->nb_samples);
int out_size = av_samples_get_buffer_size(NULL, out_ch_layout_nb, frame->nb_samples,
out_sample_fmt, 0);
fwrite(out_buffer, 1, out_size, out_file);
}
if (ret < 0) {
LOGI("error frame:%d", index);
break;
}
LOGI("decode frame :%d", index);
index++;
}
LOGI("finish");
}
使用audition打開(kāi)輸出文件mix.pcm堂鲜,可以聽(tīng)到四個(gè)文件混音后的音頻。
具體的音頻在assets目錄下护奈,可以自行對(duì)比下效果
項(xiàng)目地址