|
|
@ -4,7 +4,6 @@ |
|
|
|
#include <jni.h> |
|
|
|
#include <jni.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <android/log.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
|
|
#ifdef __cplusplus |
|
|
|
extern "C" { |
|
|
|
extern "C" { |
|
|
@ -14,6 +13,10 @@ extern "C" { |
|
|
|
#include "libavcodec/avcodec.h" |
|
|
|
#include "libavcodec/avcodec.h" |
|
|
|
#include "libswscale/swscale.h" |
|
|
|
#include "libswscale/swscale.h" |
|
|
|
#include "libswresample/swresample.h" |
|
|
|
#include "libswresample/swresample.h" |
|
|
|
|
|
|
|
#include "libavfilter/buffersink.h" |
|
|
|
|
|
|
|
#include "libavfilter/buffersrc.h" |
|
|
|
|
|
|
|
#include "libavfilter/avfiltergraph.h" |
|
|
|
|
|
|
|
#include "libavutil/opt.h" |
|
|
|
#include "ffmpeg_jni_define.h" |
|
|
|
#include "ffmpeg_jni_define.h" |
|
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
|
|
#ifdef __cplusplus |
|
|
@ -21,10 +24,114 @@ extern "C" { |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#define TAG "AudioPlayer" |
|
|
|
#define TAG "AudioPlayer" |
|
|
|
|
|
|
|
#define VOLUME_VAL 0.50 |
|
|
|
|
|
|
|
#define SLEEP_TIME 1000 * 16 |
|
|
|
#define MAX_AUDIO_FRAME_SIZE 48000 * 4 |
|
|
|
#define MAX_AUDIO_FRAME_SIZE 48000 * 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int init_filter_graph(AVFilterGraph **graph, AVFilterContext **src, AVFilterContext **sink, |
|
|
|
|
|
|
|
uint64_t channel_layout, AVSampleFormat inputFormat, int sample_rate) { |
|
|
|
|
|
|
|
AVFilterGraph *filter_graph; |
|
|
|
|
|
|
|
AVFilterContext *abuffer_ctx; |
|
|
|
|
|
|
|
const AVFilter *abuffer; |
|
|
|
|
|
|
|
AVFilterContext *volume_ctx; |
|
|
|
|
|
|
|
const AVFilter *volume; |
|
|
|
|
|
|
|
AVFilterContext *abuffersink_ctx; |
|
|
|
|
|
|
|
const AVFilter *abuffersink; |
|
|
|
|
|
|
|
AVDictionary *options_dict = NULL; |
|
|
|
|
|
|
|
uint8_t ch_layout[64]; |
|
|
|
|
|
|
|
int err; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Create a new filter graph, which will contain all the filters. */ |
|
|
|
|
|
|
|
filter_graph = avfilter_graph_alloc(); |
|
|
|
|
|
|
|
if (!filter_graph) { |
|
|
|
|
|
|
|
LOGE(TAG, "Unable to create filter graph:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Create the abuffer filter: feed data into the graph. */ |
|
|
|
|
|
|
|
abuffer = avfilter_get_by_name("abuffer"); |
|
|
|
|
|
|
|
if (!abuffer) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not find the abuffer filter:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR_FILTER_NOT_FOUND; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
abuffer_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "src"); |
|
|
|
|
|
|
|
if (!abuffer_ctx) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not allocate the abuffer instance:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Set the filter options through the AVOptions API. */ |
|
|
|
|
|
|
|
av_get_channel_layout_string(reinterpret_cast<char *>(ch_layout), sizeof(ch_layout), 0, channel_layout); |
|
|
|
|
|
|
|
av_opt_set (abuffer_ctx, "channel_layout", reinterpret_cast<char *>(ch_layout), AV_OPT_SEARCH_CHILDREN); |
|
|
|
|
|
|
|
av_opt_set (abuffer_ctx, "sample_fmt", av_get_sample_fmt_name(inputFormat), AV_OPT_SEARCH_CHILDREN); |
|
|
|
|
|
|
|
av_opt_set_q (abuffer_ctx, "time_base", (AVRational){ 1, sample_rate }, AV_OPT_SEARCH_CHILDREN); |
|
|
|
|
|
|
|
av_opt_set_int(abuffer_ctx, "sample_rate", sample_rate, AV_OPT_SEARCH_CHILDREN); |
|
|
|
|
|
|
|
err = avfilter_init_str(abuffer_ctx, NULL); |
|
|
|
|
|
|
|
if (err < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not initialize the abuffer filter:%d", stderr); |
|
|
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Create volume filter. */ |
|
|
|
|
|
|
|
volume = avfilter_get_by_name("volume"); |
|
|
|
|
|
|
|
if (!volume) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not find the volume filter:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR_FILTER_NOT_FOUND; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
volume_ctx = avfilter_graph_alloc_filter(filter_graph, volume, "volume"); |
|
|
|
|
|
|
|
if (!volume_ctx) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not allocate the volume instance:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Passing the options is as key/value pairs in a dictionary. */ |
|
|
|
|
|
|
|
av_dict_set(&options_dict, "volume", AV_STRINGIFY(VOLUME_VAL), 0); |
|
|
|
|
|
|
|
err = avfilter_init_dict(volume_ctx, &options_dict); |
|
|
|
|
|
|
|
av_dict_free(&options_dict); |
|
|
|
|
|
|
|
if (err < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not initialize the volume filter:%d", stderr); |
|
|
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Create the abuffersink filter: get the filtered data from graph. */ |
|
|
|
|
|
|
|
abuffersink = avfilter_get_by_name("abuffersink"); |
|
|
|
|
|
|
|
if (!abuffersink) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not find the abuffersink filter:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR_FILTER_NOT_FOUND; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
abuffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink, "sink"); |
|
|
|
|
|
|
|
if (!abuffersink_ctx) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not allocate the abuffersink instance:%d", stderr); |
|
|
|
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
err = avfilter_init_str(abuffersink_ctx, NULL); |
|
|
|
|
|
|
|
if (err < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Could not initialize the abuffersink instance:%d", stderr); |
|
|
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Connect the filters */ |
|
|
|
|
|
|
|
err = avfilter_link(abuffer_ctx, 0, volume_ctx, 0); |
|
|
|
|
|
|
|
if (err >= 0) |
|
|
|
|
|
|
|
err = avfilter_link(volume_ctx, 0, abuffersink_ctx, 0); |
|
|
|
|
|
|
|
if (err < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Error connecting filters:%d", stderr); |
|
|
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Configure the graph. */ |
|
|
|
|
|
|
|
err = avfilter_graph_config(filter_graph, NULL); |
|
|
|
|
|
|
|
if (err < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Error configuring the filter graph:%d", err); |
|
|
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
*graph = filter_graph; |
|
|
|
|
|
|
|
*src = abuffer_ctx; |
|
|
|
|
|
|
|
*sink = abuffersink_ctx; |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AUDIO_PLAYER_FUNC(void, play, jstring input_jstr) { |
|
|
|
AUDIO_PLAYER_FUNC(void, play, jstring input_jstr) { |
|
|
|
|
|
|
|
int got_frame = 0, ret = 0; |
|
|
|
|
|
|
|
AVFilterGraph *audioFilterGraph; |
|
|
|
|
|
|
|
AVFilterContext *audioSrcContext; |
|
|
|
|
|
|
|
AVFilterContext *audioSinkContext; |
|
|
|
|
|
|
|
|
|
|
|
const char *input_cstr = env->GetStringUTFChars(input_jstr, NULL); |
|
|
|
const char *input_cstr = env->GetStringUTFChars(input_jstr, NULL); |
|
|
|
LOGI(TAG, "input url=%s", input_cstr); |
|
|
|
LOGI(TAG, "input url=%s", input_cstr); |
|
|
|
//register all modules
|
|
|
|
//register all modules
|
|
|
@ -114,7 +221,15 @@ AUDIO_PLAYER_FUNC(void, play, jstring input_jstr) { |
|
|
|
|
|
|
|
|
|
|
|
uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRAME_SIZE); |
|
|
|
uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRAME_SIZE); |
|
|
|
|
|
|
|
|
|
|
|
int got_frame = 0, ret = 0; |
|
|
|
/* Set up the filter graph. */ |
|
|
|
|
|
|
|
AVFrame *filter_frame = av_frame_alloc(); |
|
|
|
|
|
|
|
ret = init_filter_graph(&audioFilterGraph, &audioSrcContext, &audioSinkContext, |
|
|
|
|
|
|
|
in_ch_layout, in_sample_fmt, in_sample_rate); |
|
|
|
|
|
|
|
if (ret < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Unable to init filter graph:%d", stderr); |
|
|
|
|
|
|
|
goto end; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//read audio frame
|
|
|
|
//read audio frame
|
|
|
|
while (av_read_frame(pFormatCtx, packet) >= 0) { |
|
|
|
while (av_read_frame(pFormatCtx, packet) >= 0) { |
|
|
|
//is audio stream index
|
|
|
|
//is audio stream index
|
|
|
@ -126,12 +241,26 @@ AUDIO_PLAYER_FUNC(void, play, jstring input_jstr) { |
|
|
|
} |
|
|
|
} |
|
|
|
//decode success
|
|
|
|
//decode success
|
|
|
|
if (got_frame > 0) { |
|
|
|
if (got_frame > 0) { |
|
|
|
|
|
|
|
ret = av_buffersrc_add_frame(audioSrcContext, frame); |
|
|
|
|
|
|
|
if (ret < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Error add the frame to the filter graph:%d", stderr); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/* Get all the filtered output that is available. */ |
|
|
|
|
|
|
|
ret = av_buffersink_get_frame(audioSinkContext, filter_frame); |
|
|
|
|
|
|
|
if (ret == AVERROR(EAGAIN)) { |
|
|
|
|
|
|
|
LOGE(TAG, "get filter frame again..."); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (ret < 0) { |
|
|
|
|
|
|
|
LOGE(TAG, "Error get the frame from the filter graph:%d", stderr); |
|
|
|
|
|
|
|
goto end; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//convert audio format
|
|
|
|
//convert audio format
|
|
|
|
swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRAME_SIZE, |
|
|
|
swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRAME_SIZE, |
|
|
|
(const uint8_t **) frame->data, frame->nb_samples); |
|
|
|
(const uint8_t **) /*frame*/filter_frame->data, /*frame*/filter_frame->nb_samples); |
|
|
|
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, |
|
|
|
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, |
|
|
|
frame->nb_samples, out_sample_fmt, |
|
|
|
/*frame*/filter_frame->nb_samples, out_sample_fmt, 1); |
|
|
|
1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size); |
|
|
|
jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size); |
|
|
|
jbyte *sample_byte_array = env->GetByteArrayElements(audio_sample_array, NULL); |
|
|
|
jbyte *sample_byte_array = env->GetByteArrayElements(audio_sample_array, NULL); |
|
|
@ -144,17 +273,20 @@ AUDIO_PLAYER_FUNC(void, play, jstring input_jstr) { |
|
|
|
audio_sample_array, 0, out_buffer_size); |
|
|
|
audio_sample_array, 0, out_buffer_size); |
|
|
|
//delete local reference
|
|
|
|
//delete local reference
|
|
|
|
env->DeleteLocalRef(audio_sample_array); |
|
|
|
env->DeleteLocalRef(audio_sample_array); |
|
|
|
usleep(1000 * 16); |
|
|
|
av_frame_unref(filter_frame); |
|
|
|
|
|
|
|
usleep(SLEEP_TIME); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
av_packet_unref(packet); |
|
|
|
av_packet_unref(packet); |
|
|
|
} |
|
|
|
} |
|
|
|
LOGI(TAG, "decode audio finish"); |
|
|
|
end: |
|
|
|
av_packet_free(&packet); |
|
|
|
av_packet_free(&packet); |
|
|
|
av_frame_free(&frame); |
|
|
|
av_frame_free(&frame); |
|
|
|
|
|
|
|
av_frame_free(&filter_frame); |
|
|
|
av_free(out_buffer); |
|
|
|
av_free(out_buffer); |
|
|
|
swr_free(&swrCtx); |
|
|
|
swr_free(&swrCtx); |
|
|
|
avcodec_close(codecCtx); |
|
|
|
avfilter_graph_free(&audioFilterGraph); |
|
|
|
|
|
|
|
avcodec_free_context(&codecCtx); |
|
|
|
avformat_close_input(&pFormatCtx); |
|
|
|
avformat_close_input(&pFormatCtx); |
|
|
|
env->ReleaseStringUTFChars(input_jstr, input_cstr); |
|
|
|
env->ReleaseStringUTFChars(input_jstr, input_cstr); |
|
|
|
|
|
|
|
|
|
|
|