棋牌游戏网站模板下载安装,4399观看视频免费哔哩哔哩,电脑版网站转手机版怎么做,安徽建设工程信息网平台系列文章目录
嵌入式音视频开发#xff08;零#xff09;移植ffmpeg及推流测试 嵌入式音视频开发#xff08;一#xff09;ffmpeg框架及内核解析 嵌入式音视频开发#xff08;二#xff09;ffmpeg音视频同步 嵌入式音视频开发#xff08;三#xff09;直播协议及编码器…系列文章目录
嵌入式音视频开发零移植ffmpeg及推流测试 嵌入式音视频开发一ffmpeg框架及内核解析 嵌入式音视频开发二ffmpeg音视频同步 嵌入式音视频开发三直播协议及编码器 文章目录 系列文章目录前言一、音视频同步1.1 基础概念1.2 三种同步方法 二、音视频同步的实现2.1 时间基的转换问题2.2 音频为基准2.2.1 实现思路2.2.2 代码大纲 2.3 外部时钟同步2.3.1 实现思路2.3.2 代码大纲 前言 前文中已经讲述了音视频处理的流程需要我们将音频数据和视频数据分开处理这个时候我们就需要音视频同步操作。
一、音视频同步 我们平常看视频的时候最烦恼的就是各种音画不同步例如音频是100ms延时而视频需要150ms延时才能到达这其中我们就需要进行音视频同步来解决这个问题。 音视频同步是多媒体处理中的一个关键问题常用方法包括三种不同的同步策略以视频为基准、以音频为基准和以外部时钟为基准。
1.1 基础概念 在FFmpeg进行音视频解码时PTS (Presentation Time Stamp) 是一个非常重要的概念它表示每一帧数据音频帧或视频帧的展示时间即该帧应该在播放设备上显示的精确时间。 时间基Timebase是一个分数表示每秒的时间单位。它用于将 PTS和 DTS从基于时钟滴答的计数转换为实际的时间秒。常见的表示形式为 1/fps 或 1/sample_rate例如假设视频流的时间基准是1/90000那么每个时间单位代表1/90000秒。因此PTS值为90000时相当于1秒。实际上ffmpeg内部存在多种时间基在不同的阶段结构体中对应的时间基的值都不相同。
表示方法结构体描述作用time_baseAVStream流的时间基用于将 PTS 和 DTS 转换为实际时间time_baseAVCodecContext编码器或解码器的时间基用于内部处理和同步video_codec_timebase audio_codec_timebaseAVFormatContext格式上下文的时间基用于整体管理和同步 值得注意的是虽然 AVPacket 和 AVFrame 本身没有直接的时间基字段但它们的时间戳PTS 和 DTS是基于其所属流的时间基来解释的。 时间戳可以简单理解为计时器用于记录或设置对应时间点的操作。在 FFmpeg 中时间戳用于同步音视频帧的播放时间。时间戳的计算公式如下
timestampffmpeg 内部时间戳 PTS * 时间基time秒 PTS * 时间基 例如假设我们有一个视频流其时间基为 1/90000若某帧的 PTS 值为 90000则该帧的实际展示时间为time秒 PTS * 时间基 90000 * (1/90000) 1 秒
1.2 三种同步方法 这里先简单举个例子例如下图所示原本的视频应在0.080秒有一帧但是现在出现了掉帧此时对应音频就需要加速播放或者也相应丢一帧。简单来说就是以谁为基准就由谁来维护时间轴。 1以视频为基准视频被视为主要的同步标准音频的播放时间会根据视频帧的时间戳来进行调整。如果音频的播放时间比视频快系统会延迟音频的播放为避免过多积压可能会丢弃部分音频帧如果音频播放落后于视频系统会通过延时音频的播放来保证同步。 2以音频为基准以音频为基准时视频会根据音频的时间戳进行调整。如果视频的播放时间比音频快系统会延迟视频的播放直到音频达到相应的时间点而视频播放落后于音频系统会加速视频的播放丢掉部分视频帧从而保证音视频同步。 3 以外部时钟为基准外部时钟同步是一种更为综合的方式它使用一个外部时钟例如系统时钟或硬件时钟来同时控制音频和视频的播放。外部时钟会提供一个精确的时间基准视频和音频都需要根据这个时钟进行调整。
二、音视频同步的实现
2.1 时间基的转换问题 前面提到了ffmpeg内部存在多种时间基在不同的阶段结构体中对应的时间基的值都不相同此外视频流的时间基和音频流的时间基也不同。通常情况下需要使用av_q2d()函数将AVRational 类型的时间基Timebase转换为双精度浮点数double。AVRational 是一个表示分数的结构体通常用于表示时间基、帧率等需要精确表示的比率。
typedef struct AVRational {int num; /// 分子 (numerator)int den; /// 分母 (denominator)
} AVRational;// 通过 av_q2d 函数将时间基转换为浮点数后可以将其乘以 PTS 或 DTS 来得到实际时间
double av_q2d(AVRational q) {return q.num / (double)q.den;
}2.2 音频为基准 音频为基准和视频为基准在实现逻辑上差不多这里以音频为例。
2.2.1 实现思路 以音频为基准进行同步的基本思路是
选择音频流作为同步基准解码音频数据并更新当前音频时间戳解码视频数据并根据音频时间戳调整视频帧的显示时间确保音视频同步通过适当的缓冲控制确保播放的流畅性和稳定性
2.2.2 代码大纲
int main{// 初始化 FFmpeg 库av_register_all();AVFormatContext *fmt_ctx NULL;// 打开输入文件并获取流信息if (open_input_file(fmt_ctx, input.mp4) 0) {return -1;}// 查找音视频流并初始化解码器int audio_stream_idx find_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO);int video_stream_idx find_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO);AVCodecContext *audio_dec_ctx init_decoder(fmt_ctx, audio_stream_idx);AVCodecContext *video_dec_ctx init_decoder(fmt_ctx, video_stream_idx);// 循环读取数据包并同步播放AVPacket pkt;while (read_packet(fmt_ctx, pkt) 0) {if (pkt.stream_index audio_stream_idx) {process_audio_packet(pkt, audio_dec_ctx);} else if (pkt.stream_index video_stream_idx) {process_video_packet(pkt, video_dec_ctx, audio_dec_ctx-time_base);}av_packet_unref(pkt);}// 清理资源cleanup(fmt_ctx, audio_dec_ctx, video_dec_ctx);
}// 解码音频数据包并更新当前音频时间戳
void process_audio_packet(AVPacket *pkt, AVCodecContext *dec_ctx) {int ret avcodec_send_packet(dec_ctx, pkt);if (ret 0) {fprintf(stderr, Error sending a packet for decoding\n);return;}while (ret 0) {ret avcodec_receive_frame(dec_ctx, frame);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0) {fprintf(stderr, Error during decoding\n);break;}// 更新当前音频时间戳update_current_audio_pts(frame-pts, dec_ctx-time_base);}
}void update_current_audio_pts(int64_t pts, AVRational time_base) {double pts_in_seconds pts * av_q2d(time_base);current_audio_pts pts_in_seconds;
}void process_video_packet(AVPacket *pkt, AVCodecContext *dec_ctx, AVRational audio_time_base) {int ret avcodec_send_packet(dec_ctx, pkt);if (ret 0) {fprintf(stderr, Error sending a packet for decoding\n);return;}while (ret 0) {ret avcodec_receive_frame(dec_ctx, frame);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0) {fprintf(stderr, Error during decoding\n);break;}// 获取视频帧的 PTS 并转换为秒double video_pts_in_seconds frame-pts * av_q2d(dec_ctx-time_base);// 根据音频时间戳调整视频帧的显示时间sync_video_to_audio(video_pts_in_seconds, audio_time_base);}
}void sync_video_to_audio(double video_pts, AVRational audio_time_base) {while (video_pts current_audio_pts) {usleep(1000); // 简单的等待机制// 更新当前音频时间戳current_audio_pts get_current_audio_pts(audio_time_base);// 其他操作}
}double get_current_audio_pts(AVRational audio_time_base) {// 这里应该实现一个函数来获取最新的音频时间戳// 例如通过解码更多的音频帧或使用其他方法return current_audio_pts;
}2.3 外部时钟同步
2.3.1 实现思路 以外部时钟为基准进行同步的基本思路是
使用外部时钟如系统时钟作为基准解码音频数据包根据外部时钟调整音频播放时间解码视频数据包根据外部时钟调整视频帧的显示时间通过适当的缓冲控制确保播放的流畅性和稳定性
2.3.2 代码大纲 这里的代码和上文差不多只有调整部分的逻辑不太一样
// 获取当前外部时钟时间秒
double get_external_clock() {struct timespec now;clock_gettime(CLOCK_MONOTONIC, now); // 使用单调递增的时钟避免系统时间变化的影响double elapsed (now.tv_sec - start_time.tv_sec) (now.tv_nsec - start_time.tv_nsec) / 1e9;return elapsed;
}// 解码音频数据包并根据外部时钟调整音频播放时间
void process_audio_packet(AVPacket *pkt, AVCodecContext *dec_ctx) {int ret avcodec_send_packet(dec_ctx, pkt);if (ret 0) {fprintf(stderr, Error sending a packet for decoding\n);return;}while (ret 0) {ret avcodec_receive_frame(dec_ctx, frame);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0) {fprintf(stderr, Error during decoding\n);break;}// 将音频帧的时间戳转换为秒double audio_pts_in_seconds frame-pts * av_q2d(dec_ctx-time_base);// 根据外部时钟调整音频帧的播放时间sync_audio_to_external_clock(audio_pts_in_seconds, dec_ctx-time_base);}
}void sync_audio_to_external_clock(double audio_pts, AVRational time_base) {double external_clock_time get_external_clock(); // 获取外部时钟时间秒// 等待直到音频帧应该播放的时间while (audio_pts external_clock_time) {usleep(1000); // 简单的等待机制external_clock_time get_external_clock();}// 其他操作
}void process_video_packet(AVPacket *pkt, AVCodecContext *dec_ctx, AVRational audio_time_base) {int ret avcodec_send_packet(dec_ctx, pkt);if (ret 0) {fprintf(stderr, Error sending a packet for decoding\n);return;}while (ret 0) {ret avcodec_receive_frame(dec_ctx, frame);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0) {fprintf(stderr, Error during decoding\n);break;}// 获取视频帧的 PTS 并转换为秒double video_pts_in_seconds frame-pts * av_q2d(dec_ctx-time_base);// 根据外部时钟调整视频帧的显示时间sync_video_to_external_clock(video_pts_in_seconds, dec_ctx-time_base);}
}void sync_video_to_external_clock(double video_pts, AVRational video_time_base) {double external_clock_time get_external_clock(); // 获取外部时钟时间秒// 等待直到视频帧应该显示的时间while (video_pts external_clock_time) {usleep(1000); // 简单的等待机制external_clock_time get_external_clock();}// 其他操作
}免责声明本文参考了网上公开的部分资料仅供学习参考使用若有侵权或勘误请联系笔者