1.为什么音视频同步。
先来说说为什么要音视频同步。以 mp4 为例,mp4 就好比是一个 zip 包, 是封装格式,里面有着被压缩的视频和音频,并且它们二个是分开的不是在一起的,而且视频和音频的数量和顺序不是固定统一的,这就造成了在解压缩 mp4 数据时得到的音频 package 和视频 package 数据的数量是不固定的。那么之后在解码 package 数据时得到的 frame 数据也是的,如果在这里不做音视频同步的话,就造成了解码一个 frame 就马上进行播放音频或渲染到屏幕上,毫无时间的概念,解码多块就播放多块。其次目前手机的配置一般较高,这就造成了可能播放视频或渲染的速度小于解码的速度,之后解码的数据就越来越多就会造成内存不断上涨等。
2.音视频同步为什么要以音频的时间为准
对于视频来说(以h264为例),视频中有着I帧,P帧,B帧之分,每个帧数据里面又有pts(时间信息)。这些的出现是为了减少视频的大小做出的帧间预测。对于mp4的解码顺序而言是IPB..等,是是先解码I帧,之后解码P帧(数量大于等于1),最后解码B帧(数量大于1)。而对于视频的播放顺序而言是I帧B帧(数量大于等于1)P帧B帧..。
总的来说,对视频解码的顺序和视频的播放顺序是不一样的,这就导致了如果解码一帧播放一帧的话,视频帧的时间有可能是跳着的,而播放的时间又是顺序的。这就导致了不能以视频的时间为准来同步音频时间。
而在音频里面没有所谓的帧间预测,音频的原始数据是pcm,压缩数据是wav或者aac。解码音频后的数据顺序就是编码音频的顺序,音频数据里面的pts是顺序排列的,解码顺序也就是播放顺序。
所以音视频同步为什么要以音频的时间为准
3.暂停操作
1.判断当前是不是在播放,没有在播放就没操作了。。。
2.当前在播放中,给各个类设置 isplay = false,这样的话就可以使解码 package 循环和 解码 frame 循环停止下来,视频和音频的渲染和播放也就停了。
3.最后别忘了给 open sles 设置播放停止
void FFmpegPlayer::pause() {
if (audioChannel && videoChannel && isPlaying) {
isPlaying = false;
isPause = true;
audioChannel->pause();
videoChannel->pause();
javaCallHelper->call_java_status(THREAD_MAIN, PLAYER_PAUSE);
}
}
void VideoChannel::pause() {
isPlaying = false;
// this->package_queue.setWork(false);
// this->frame_queue.setWork(false);
}
void AudioChannel::pause() {
isPlaying = false;
// this->package_queue.setWork(false);
// this->frame_queue.setWork(false);
//5.设置播放状态
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
}
4.继续播放操作
1.判断当前是不是在播放,在播放或者没有开始就没操作了。。。
2.先给各个类设置 isplay = true,在将解码 package 线程和 解码 frame 线程重新启动。
3.给 open sles 设置播放
void FFmpegPlayer::resume() {
if (audioChannel && videoChannel && !isPlaying) {
isPlaying = true;
isPause = false;
if (audioChannel) {
audioChannel->resume();
}
if (videoChannel) {
videoChannel->resume();
}
pthread_create(&play_pid, NULL, pthread_ffmpeg_play, this);
pthread_detach(play_pid);
javaCallHelper->call_java_status(THREAD_MAIN, PLAYER_PLAYING);
}
}
void VideoChannel::resume() {
play();
}
void VideoChannel::play() {
this->package_queue.setWork(true);
this->frame_queue.setWork(true);
this->isPlaying = true;
pthread_create(&decode_pid, NULL, pthread_video_decode, this);
pthread_create(&play_pid, NULL, pthread_video_play, this);
pthread_detach(play_pid);
pthread_detach(decode_pid);
}
void AudioChannel::resume() {
/**
* TODO 疑惑 为什么加上下面两句话在 pause 后 resume 会 anr
*/
// this->package_queue.setWork(true);
// this->frame_queue.setWork(true);
this->isPlaying = true;
pthread_create(&decode_pid, NULL, pthread_audio_decode, this);
pthread_detach(decode_pid);
//5.设置播放状态
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
}
5.视频快进/退/进度跳转
主要是用到了 avformat_seek_file()这个函数
/*
* @param AVFormatContext 这个不解释
* @param stream_index 选择哪个流进行 seek 操作,如果传 -1 表示选择所有流
* @param min_ts 跳转最小值
* @param ts 跳转的目标时间,注意时间的单位是 微妙(us) 1 s = 1000000 us
* @param max_ts 跳转最大值
* @param flags 标志位 可选的值为
* 1. AVSEEK_FLAG_BYTE, then all timestamps are in bytes and are the file position (this may not be supported by all demuxers).
* 2. AVSEEK_FLAG_FRAME, then all timestamps are in frames in the stream with stream_index (this may not be supported by all demuxers).
* Otherwise all timestamps are in units of the stream selected by stream_index or if stream_index is -1, in AV_TIME_BASE units.
* 3. AVSEEK_FLAG_ANY, then non-keyframes are treated as keyframes (this may not be supported by all demuxers).
* 4. AVSEEK_FLAG_BACKWARD, it is ignored.
* @return >=0 on success, error code otherwise
*/
int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
步骤:
1.判断当前是不是在播放,没有在播放就没操作了。。。
2.调用 avformat_seek_file()
3.将之前的音频和视频的 package 队列和 解码 frame 队列丢弃掉
void FFmpegPlayer::seek(int time) {
if (!isPlaying) {
return;
}
time = (time + 3) * AV_TIME_BASE;
time += formatContext->start_time;
LOGE("FFmpegPlayer::seek time is %d", time);
if (avformat_seek_file(formatContext, -1, INT64_MIN, time, INT64_MAX, AVSEEK_FLAG_BACKWARD) < 0) {
LOGE("avformat_seek_file error");
return;
}
if (audioChannel) {
audioChannel->seek(time);
}
if (videoChannel) {
videoChannel->seek(time);
}
}
void VideoChannel::seek(int time) {
/**
* TODO 晚上说先 avformat_seek_file 再 avcodec_flush_buffers
*/
// avcodec_flush_buffers(avCodecContext);
package_queue.clear();
frame_queue.clear();
}
void AudioChannel::seek(int time) {
// avcodec_flush_buffers(avCodecContext);
//要不要让队列停止工作
package_queue.clear();
frame_queue.clear();
//要不要让队列恢复工作
}
6.倍数播放
对于视频来说是可以播放一帧丢弃一帧,这个方法是可行的。但是对于音频来说就是一个灾难,效果巨差。查资料是要对音频进行重采样才可以,当时这个需要引入另外的库文件,就先放着之后在看看有没有什么更好的方法实现
