FFmpeg音频播放器(9)-播放控制

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(&not_full);
    pthread_mutex_unlock(&mutex);
}

释放资源

void AudioPlayer::release() {
    pthread_mutex_lock(&mutex);
    isPlay = 0;
    pthread_cond_signal(&not_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

播放测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357