美文网首页
FFmpeg音频播放器(9)-播放控制

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

作者: 星星y | 来源:发表于2019-01-25 11:48 被阅读0次

    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

    播放测试

    相关文章

      网友评论

          本文标题:FFmpeg音频播放器(9)-播放控制

          本文链接:https://www.haomeiwen.com/subject/qyrmjqtx.html