Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能
实现快进、快退功能,其实就是设置视频的播放进度!
基本思路如下:
思路很简单,我们一步一步实现!
这里,我结合线程锁和条件变量来实现,暂时生产和消费:
/**
* 生产函数
* 循环读取帧 解码 丢到对应的队列中
* @param arg
* @return
*/
void* produce(void* arg) {
for (;;) {
pthread_mutex_lock(&seek_mutex);
while (is_seek) {
LOGE("Player Log : produce waiting seek");
pthread_cond_wait(&seek_condition, &seek_mutex);
LOGE("Player Log : produce wake up seek");
}
pthread_mutex_unlock(&seek_mutex);
...
}
...
return NULL;
}
/**
* 消费函数
* 从队列获取解码数据 同步播放
* @param arg
* @return
*/
void* consume(void* arg) {
...
for (;;) {
...
pthread_mutex_lock(&seek_mutex);
while (is_seek) {
pthread_cond_wait(&seek_condition, &seek_mutex);
}
pthread_mutex_unlock(&seek_mutex);
...
}
return NULL;
}
/**
* 快进/快退
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_seekTo(JNIEnv *env, jobject instance, jint progress) {
is_seek = true;
pthread_mutex_lock(&seek_mutex);
...
is_seek = false;
pthread_cond_broadcast(&seek_condition);
pthread_mutex_unlock(&seek_mutex);
}
/**
* 清空队列
* @param queue
*/
void queue_clear(Queue* queue) {
pthread_mutex_lock(queue->mutex_id);
Node* node = queue->head;
while (node != NULL) {
queue->head = queue->head->next;
free(node);
node = queue->head;
}
queue->head = NULL;
queue->tail = NULL;
queue->size = 0;
queue->is_block = true;
pthread_cond_signal(queue->not_full_condition);
pthread_mutex_unlock(queue->mutex_id);
}
/**
* 快进/快退
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_seekTo(JNIEnv *env, jobject instance, jint progress) {
...
queue_clear(cplayer->video_queue);
queue_clear(cplayer->audio_queue);
...
}
记得唤醒生产函数!!!我就是没有唤醒,导致假死锁!
/**
* 快进/快退
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_seekTo(JNIEnv *env, jobject instance, jint progress) {
...
int result = av_seek_frame(cplayer->format_context, cplayer->video_stream_index, (int64_t) (progress / av_q2d(cplayer->format_context->streams[cplayer->video_stream_index]->time_base)), AVSEEK_FLAG_BACKWARD);
if (result < 0) {
LOGE("Player Error : Can not seek video to %d", progress);
return;
}
result = av_seek_frame(cplayer->format_context, cplayer->audio_stream_index, (int64_t) (progress / av_q2d(cplayer->format_context->streams[cplayer->audio_stream_index]->time_base)), AVSEEK_FLAG_BACKWARD);
if (result < 0) {
LOGE("Player Error : Can not seek audio to %d", progress);
return;
}
...
}
我传入的 progress 参数的单位是秒,而 av_seek_frame 的第3个参数单位是 time_base 时间基,所以做这样的运算:
progress / av_q2d(stream->time_base)
/**
* 快进/快退
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_seekTo(JNIEnv *env, jobject instance, jint progress) {
...
pthread_cond_broadcast(&seek_condition);
...
}
用 pthread_cond_broadcast 唤醒,让生产和消费可以继续!
/**
* 快进/快退
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_seekTo(JNIEnv *env, jobject instance, jint progress) {
is_seek = true;
pthread_mutex_lock(&seek_mutex);
queue_clear(cplayer->video_queue);
queue_clear(cplayer->audio_queue);
int result = av_seek_frame(cplayer->format_context, cplayer->video_stream_index, (int64_t) (progress / av_q2d(cplayer->format_context->streams[cplayer->video_stream_index]->time_base)), AVSEEK_FLAG_BACKWARD);
if (result < 0) {
LOGE("Player Error : Can not seek video to %d", progress);
return;
}
result = av_seek_frame(cplayer->format_context, cplayer->audio_stream_index, (int64_t) (progress / av_q2d(cplayer->format_context->streams[cplayer->audio_stream_index]->time_base)), AVSEEK_FLAG_BACKWARD);
if (result < 0) {
LOGE("Player Error : Can not seek audio to %d", progress);
return;
}
is_seek = false;
pthread_cond_broadcast(&seek_condition);
pthread_mutex_unlock(&seek_mutex);
}
代码地址:https://github.com/JohanMan/Player