FFmpeg音频播放器(1)-简介
FFmpeg音频播放器(2)-编译动态库
FFmpeg音频播放器(3)-将FFmpeg加入到Android中
FFmpeg音频播放器(4)-将mp3解码成pcm
FFmpeg音频播放器(5)-单输入filter(volume,atempo)
FFmpeg音频播放器(6)-多输入filter(amix)
FFmpeg音频播放器(7)-使用OpenSLES播放音频
FFmpeg音频播放器(8)-创建FFmpeg播放器
FFmpeg音频播放器(9)-播放控制
前面一节我们已经创建了一个基于FFmpeg的播放器,这一节开始对播放器进行各种控制操作。主要有调音,变速,暂停,播放,进度切换,停止(释放资源)。
首先在java层创建FFmpegAudioPlayer.kt(kotlin),加入以下方法用于jni
class FFmpegAudioPlayer {
/**
* 初始化
*/
external fun init(paths: Array<String>)
/**
* 播放
*/
external fun play()
/**
* 暂停
*/
external fun pause()
/**
* 释放资源
*/
external fun release()
/**
* 修改每个音量
*/
external fun changeVolumes(volumes: Array<String>)
/**
* 变速
*/
external fun changeTempo(tempo: String)
/**
* 总时长 秒
*/
external fun duration(): Double
/**
* 当前进度 秒
*/
external fun position(): Double
/**
* 进度跳转
*/
external fun seek(sec: Double)
companion object {
init {
System.loadLibrary("avutil-55")
System.loadLibrary("swresample-2")
System.loadLibrary("avcodec-57")
System.loadLibrary("avfilter-6")
System.loadLibrary("swscale-4")
System.loadLibrary("avformat-57")
System.loadLibrary("native-lib")
}
}
}
然后在jni层,实现对应的方法。
#include "AudioPlayer.h"
static AudioPlayer *player;
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_init(
JNIEnv *env,
jobject /* this */, jobjectArray _srcs) {
//将java传入的字符串数组转为c字符串数组
jsize len = env->GetArrayLength(_srcs);
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));
}
player = new AudioPlayer(pathArr, len);
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_changeVolumes(
JNIEnv *env,
jobject /* this */, jobjectArray _volumes) {
//将java传入的字符串数组转为c字符串数组
jsize len = env->GetArrayLength(_volumes);
int i = 0;
for (i = 0; i < len; i++) {
jstring str = static_cast<jstring>(env->GetObjectArrayElement(_volumes, i));
char *volume = const_cast<char *>(env->GetStringUTFChars(str, 0));
player->volumes[i] = volume;
}
player->change = 1;
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_changeTempo(
JNIEnv *env,
jobject /* this */, jstring _tempo) {
char *tempo = const_cast<char *>(env->GetStringUTFChars(_tempo, 0));
player->tempo = tempo;
player->change = 1;
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_play(
JNIEnv *env,
jobject /* this */) {
player->play();
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_pause(
JNIEnv *env,
jobject /* this */) {
player->pause();
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_release(
JNIEnv *env,
jobject /* this */) {
player->release();
}
extern "C" JNIEXPORT void
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_seek(
JNIEnv *env,
jobject /* this */, jdouble secs) {
player->seek(secs);
}
extern "C" JNIEXPORT jdouble
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_duration(
JNIEnv *env,
jobject /* this */) {
return player->total_time;
}
extern "C" JNIEXPORT jdouble
JNICALL
Java_io_github_iamyours_ffmpegaudioplayer_FFmpegAudioPlayer_position(
JNIEnv *env,
jobject /* this */) {
return player->current_time;
}
最终的实现在AudioPlayer.cpp中
调音,变速
为了能够实现变速,调音,我们要在解码之前重新修改过滤器的参数。这里使用一个change参数作为标记,表明需要重新初始化filter,初始化完成后,把change重新修改成0。
int AudioPlayer::initFilters() {
LOGI("init filters");
if (change)avfilter_graph_free(&graph);
graph = avfilter_graph_alloc();
...
change = 0;
return 1;
}
这里需要将之前的过滤器资源释放掉,以免内存溢出。
在解码之前,通过change标志,重新初始化。
void AudioPlayer::decodeAudio() {
...
while (isPlay) {
LOGI("decode frame:%d", index);
if (change) {
initFilters();
}
for (int i = 0; i < fileCount; i++) {
AVFormatContext *fmt_ctx = fmt_ctx_arr[i];
ret = av_read_frame(fmt_ctx, packet);
if (packet->stream_index != stream_index_arr[i])continue;
...
ret = av_buffersrc_add_frame(srcs[i], frame);
if (ret < 0) {
LOGE("error add frame to filter");
goto end;
}
}
while (av_buffersink_get_frame(sink, frame) >= 0) {
frame->pts = packet->pts;
put(frame);
}
index++;
}
end:
...
}
这样就可以实现音量和速度的控制了。
暂停,播放
暂停可以通过OpenSLES播放器接口通过设置暂停状态来暂停播放。设置此状态后,缓冲回调就会暂停回调。
void AudioPlayer::pause() {
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
}
而重新播放我们也只需要设置播放中SL_PLAYSTATE_PLAYING状态
void AudioPlayer::play() {
LOGI("play...");
if (isPlay) {
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
return;
}
isPlay = 1;
seek(0);
pthread_create(&decodeId, NULL, _decodeAudio, this);
pthread_create(&playId, NULL, _play, this);
}
进度控制
进度控制是使用av_seek_frame来实现,使用av_q2d将秒数转为ffmpeg内部的时间戳
void AudioPlayer::seek(double secs) {
pthread_mutex_lock(&mutex);
for (int i = 0; i < fileCount; i++) {
av_seek_frame(fmt_ctx_arr[i], stream_index_arr[i], (int64_t) (secs / av_q2d(time_base)),
AVSEEK_FLAG_ANY);
}
current_time = secs;
queue.clear();
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
}
释放资源
void AudioPlayer::release() {
pthread_mutex_lock(&mutex);
isPlay = 0;
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
if (playItf)(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
if (playerObject) {
(*playerObject)->Destroy(playerObject);
playerObject = 0;
bufferQueueItf = 0;
}
if (mixObject) {
(*mixObject)->Destroy(mixObject);
mixObject = 0;
}
if (engineObject) {
(*engineObject)->Destroy(engineObject);
engineItf = 0;
}
if (swr_ctx) {
swr_free(&swr_ctx);
}
if (graph) {
avfilter_graph_free(&graph);
}
for (int i = 0; i < fileCount; i++) {
avcodec_close(codec_ctx_arr[i]);
avformat_close_input(&fmt_ctx_arr[i]);
}
free(codec_ctx_arr);
free(fmt_ctx_arr);
LOGI("release...");
}
设置播放器状态为停止,释放Open SLES相关资源,释放过滤器资源,释放解码器资源,关闭输入流。
项目地址
播放时需要将assets的音频文件放到对应sd卡目录
https://github.com/iamyours/FFmpegAudioPlayer
网友评论