ffmpeg 同步
音视频同步有三种方式
- 同步音频到视频
- 同步到外部时钟
- 同步视频到音频
同步视频到音频
- 以audio为基准同步video,只要设置好了 ao 的参数,如sample rate, channels, sample size等, audio驱动就能以正确的速度播放,所以只要程序里write不出大问题的话,这种同步是非常有效的。
- 设置以声音为基准进行同步,声频播放是只管自己播放,视频每渲染一张图片取到的avframe中对应的时间戳和音频适中的时间戳进行对比,调整视频的渲染速度。
- 设置声音为2倍速度进行播放,让视频同步音频,测试可以完美支持。
- 音频去同步视频设置多倍速度播放,无法有简单的方式指定视频严格的按照两倍的速度进行播放。
- 三个clock,一个音频的,一个视频,一个中立的。
- 以视频为准,AudioQueue每读取一音频帧,读取当前帧中的时间戳,并通过到中立的clock中。
- 播放视频的时候,每渲染一个视频帧都需要拿videoclock和中立的clock进行对比,来决定是否要进行渲染。
二倍速度播放音频
-
AVPlayer 修改视频的播放速度
属性:
/* indicates the current rate of playback; 0.0 means "stopped", 1.0 means "play at the natural rate of the current item" */ @property (nonatomic) float rate;
实例代码:
-(void)setPlaybackRate:(float)playbackRate { _playbackRate = playbackRate; if (_player != nil && !isFloatZero(_player.rate)) { _player.rate = _playbackRate; } }
-
AudioQueue修改视频的播放速度
实例代码:
- (void)setPlaybackRate:(float)playbackRate
{
if (fabsf(playbackRate - 1.0f) <= 0.000001) {
UInt32 propValue = 1;
AudioQueueSetProperty(_audioQueueRef, kAudioQueueProperty_TimePitchBypass, &propValue, sizeof(propValue));
AudioQueueSetParameter(_audioQueueRef, kAudioQueueParam_PlayRate, 1.0f);
} else {
UInt32 propValue = 0;
AudioQueueSetProperty(_audioQueueRef, kAudioQueueProperty_TimePitchBypass, &propValue, sizeof(propValue));
AudioQueueSetParameter(_audioQueueRef, kAudioQueueParam_PlayRate, playbackRate);
}
}
pts
视频读取pts
- 函数实例:
ret = avcodec_decode_video2(d->avctx, frame, &got_frame, &d->pkt_temp);
if (got_frame) {
ffp->stat.vdps = SDL_SpeedSamplerAdd(&ffp->vdps_sampler, FFP_SHOW_VDPS_AVCODEC, "vdps[avcodec]");
if (ffp->decoder_reorder_pts == -1) {
frame->pts = av_frame_get_best_effort_timestamp(frame);
} else if (ffp->decoder_reorder_pts) {
frame->pts = frame->pkt_pts;
} else {
frame->pts = frame->pkt_dts;
}
}
- 视频获取时间戳
int64_t av_frame_get_best_effort_timestamp(const AVFrame *frame);
音频读取pts
函数实例:
ret = avcodec_decode_audio4(d->avctx, frame, &got_frame, &d->pkt_temp);
if (got_frame) {
AVRational tb = (AVRational){1, frame->sample_rate};
if (frame->pts != AV_NOPTS_VALUE)
frame->pts = av_rescale_q(frame->pts, d->avctx->time_base, tb);
else if (frame->pkt_pts != AV_NOPTS_VALUE)
frame->pts = av_rescale_q(frame->pkt_pts, av_codec_get_pkt_timebase(d->avctx), tb);
else if (d->next_pts != AV_NOPTS_VALUE)
frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
if (frame->pts != AV_NOPTS_VALUE) {
d->next_pts = frame->pts + frame->nb_samples;
d->next_pts_tb = tb;
}
}
音频一个AVPacket中只解析一个AVFrame, pkt_pts 可以当做真实的pts
流的时间基准
从某个音频或者视频流的时间基准中得到时间戳的 AVRational
AVRational av_codec_get_pkt_timebase (const AVCodecContext *avctx);
FrameQueue
typedef struct FrameQueue {
Frame queue[FRAME_QUEUE_SIZE];
int rindex; // read index
int windex; // write index
int size; // 大小
int max_size;
int keep_last;
int rindex_shown; // read shown 已经读取的个数
SDL_mutex *mutex;
SDL_cond *cond;
PacketQueue *pktq;
} FrameQueue;
几个重要的函数
- synchronize_audio 音频clock校准函数
- video_refresh 视频的渲染调用
- compute_target_delay 计算音频和视频的delay
- update_video_pts 同步视频的时间戳
- audio_decode_frame 读取音频数据后同步音频时钟
- video_refresh_thread remaining_time决定每次休眠时
- 还重写了分配帧和销毁帧的方法:
-
C/C++ code
static int my_get_buffer(struct AVCodecContext *c, AVFrame *pic){ int ret= avcodec_default_get_buffer(c, pic); uint64_t *pts= av_malloc(sizeof(uint64_t)); *pts= global_video_pkt_pts; pic->opaque= pts; return ret; } static void my_release_buffer(struct AVCodecContext *c, AVFrame *pic){ if(pic) av_freep(&pic->opaque); avcodec_default_release_buffer(c, pic);
在使用这个全局时间戳的时候,是这样使用的:
-
C/C++ code
/* NOTE: ipts is the PTS of the _first_ picture beginning in this packet, if any */ global_video_pkt_pts= pkt->pts; len1 = avcodec_decode_video(is->video_st->codec, frame, &got_picture, pkt->data, pkt->size); if( (decoder_reorder_pts || pkt->dts == AV_NOPTS_VALUE) && frame->opaque && *(uint64_t*)frame->opaque != AV_NOPTS_VALUE) pts= *(uint64_t*)frame->opaque; else if(pkt->dts != AV_NOPTS_VALUE) pts= pkt->dts; else pts= 0; pts *= av_q2d(is->video_st->time_base); // if (len1 < 0) // break; if (got_picture) { if (output_picture2(is, frame, pts) < 0) goto the_end; } av_free_packet(pkt); if (step) if (cur_stream) stream_pause(cur_stream);