Android使用FFmpeg(七)--ffmpeg實現暫停、快退快進播放

Android使用FFmpeg(一)--編譯ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg實現視頻播放
Android使用FFmpeg(四)--ffmpeg實現音頻播放(使用AudioTrack進行播放)
Android使用FFmpeg(五)--ffmpeg實現音頻播放(使用openSL ES進行播放)
Android使用FFmpeg(六)--ffmpeg實現音視頻同步播放
Android使用FFmpeg(七)--ffmpeg實現暫停、快退快進播放java

前言

這篇文章關於ffmpeg的解決方案僅提供參考,若有錯誤,歡迎指正。ide

播放、暫停

在音視頻播放的時候,咱們均是將讀取到的Packet放入到隊列中,而後再從隊列中取出packet進行解碼播放。因此在播放、暫停時能夠在隊列的取出上面作文章。函數

int FFmpegMusic::get(AVPacket *avPacket) {
    LOGE("取出隊列")
    pthread_mutex_lock(&mutex);
    while (isPlay){
        if(!queue.empty()&&isPause){
            LOGE("ispause %d",isPause);
            //若是隊列中有數據能夠拿出來
            if(av_packet_ref(avPacket,queue.front())){
                break;
            }
            //取成功了,彈出隊列,銷燬packet
            AVPacket *packet2 = queue.front();
            queue.pop();
            av_free(packet2);
            break;
        } else{
            LOGE("音頻執行wait")
            LOGE("ispause %d",isPause);
            pthread_cond_wait(&cond,&mutex);

        }
    }
    pthread_mutex_unlock(&mutex);
    return 0;
}

如上,咱們控制isPause的值來讓線程進入wait狀態,若是須要繼續播放的話需喚醒線程,繼續從隊列中取出packetspa

if(isPause==1){
        isPause=0;
    } else{
        isPause=1;
        pthread_cond_signal(&cond);
    }

進度條

進度條在java層使用的SeekBar,須要取到視頻的總時間以及播放的時間。
視頻總時間,注意是在獲取視頻信息(avformat_find_stream_info(pFormatCtx, NULL))事後才能獲得總時間:線程

//獲得播放總時間
    if(pFormatCtx->duration != AV_NOPTS_VALUE){
        duration = pFormatCtx->duration;//微秒
    }

由於播放時是以音頻爲基礎,因此須要獲得音頻的播放時間,由於在播放音頻時有單獨記錄音頻的播放時間,因此咱們能夠直接獲得:code

return ffmpegMusic->clock;

在java層實時繪製取到音頻播放時間並繪製進度,while(true)的條件爲是否正在播放,這裏爲了方便,因此這樣寫:orm

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Message message = new Message();
                        message.what = (int) Player.getCurrentPosition()*1000;
                        handler.sendMessage(message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
            mSeekBar.setProgress(msg.what);
        }
    };

快退、快進

主要涉及到跳幀函數:int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
AVFormatContext *s:封裝格式上下文
int stream_index:音頻流或者視頻流
int64_t timestamp:時間,若是流爲-1,那麼時間的單位爲AV_TIME_BASE;若是爲具體的流,那麼時間單位爲AVStream.time_base 。
int flags:功能flag
大概思路:
清空隊列-->av_seek_frame-->av_read_frame-->put隊列
注意:由於系統自帶的隊列沒有clear函數,若是使用queue.pop將會使效率很是慢,會有卡頓跡象,因此做者將queue換成了vector,這樣能夠直接clear,在清空隊列的時候效率大大提升。視頻

void seekTo(int mesc) {
    if (mesc <= 0) {
        mesc=0;
    }
    //清空vector
    ffmpegMusic->queue.clear();
    ffmpegVideo->queue.clear();
    //跳幀
    //-1表示默認流
    if (av_seek_frame(pFormatCtx, -1,  mesc * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD) < 0) {
        LOGE("failed")
    } else {
        LOGE("success")
    }
    //若是是確認某一個流,
av_seek_frame(pFormatCtx, ffmpegVideo->index, (int64_t) (mesc /av_q2d(ffmpegVideo->time_base)), AVSEEK_FLAG_BACKWARD);
    av_seek_frame(pFormatCtx, ffmpegMusic->index, (int64_t) (mesc /av_q2d(ffmpegMusic->time_base)), AVSEEK_FLAG_BACKWARD);
}
//while循環一直在線程中執行
 while (isPlay) {
        ret = av_read_frame(pFormatCtx, packet);
        if (ret == 0) {
            if (ffmpegVideo && ffmpegVideo->isPlay && packet->stream_index == ffmpegVideo->index
               ) {
                //將視頻packet壓入隊列
                ffmpegVideo->put(packet);
            } else if (ffmpegMusic && ffmpegMusic->isPlay &&
                       packet->stream_index == ffmpegMusic->index) {
                ffmpegMusic->put(packet);
            }
            av_packet_unref(packet);
        } else if (ret == AVERROR_EOF) {
            // 讀完了
            //讀取完畢 可是不必定播放完畢
            while (isPlay) {
                if (ffmpegVideo->queue.empty() && ffmpegMusic->queue.empty()) {
                    break;
                }
                // LOGE("等待播放完成");
                av_usleep(10000);
            }
        }
    }

須要跳轉時直接調用這個函數便可:隊列

seekTo(gettime/1000);//將毫秒轉爲秒
相關文章
相關標籤/搜索