オーディオおよびビデオプレーヤー(II)を開発する方法
最後の講義内容を続行。オーディオおよびビデオプレーヤー(ffmpeg3.2 + sdl2.0、映像+音声)を開発する方法
第四に、オーディオとビデオのキューから抽出されたパケット、およびデコード
オーディオフレームをデコードして再生します
双方向オーディオのデコードにはほとんど変化があります。
// decode_audio_thread 解码音频packet的线程
void decode_audio_thread(PlayerContext* playerCtx) {
AVFrame *pFrame = nullptr;
while (playerCtx && !playerCtx->quit) {
AVPacket *pkt;
if (!playerCtx->audio_queue.pull(pkt)) { // 从音频队列中取出音频包
SDL_Delay(1);
continue;
}
// ------------------------ 根据音频包解码出一个音频帧 ------------------------ //
if (avcodec_send_packet(playerCtx->audioCodecCtx, pkt)) {// 发送一个pkt给解码器,ffmpeg3之后的写法
exit(1);
return;
}
int ret = 0;
while (ret >= 0) {
if (!pFrame) {
if (!(pFrame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
// 从编码器接收一个frame
ret = avcodec_receive_frame(playerCtx->audioCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
{
fprintf(stderr, "Error during decoding\n");
exit(1);
return;
}
// ----------------------- 对音频帧进行重采样,并给系统缓冲区数据 -------------------------- //
swr_convert(playerCtx->au_convert_ctx, &playerCtx->out_buffer, playerCtx->out_buffer_size, // 对音频的采样率进行转换
(const uint8_t * *)pFrame->data, playerCtx->audioCodecCtx->frame_size);
// 上一帧音频数据还没有播完
while (playerCtx->audio_len > 0) {
SDL_Delay(1);
}
// 重新设置音频缓存
playerCtx->audio_pos = (Uint8*)playerCtx->out_buffer;
playerCtx->audio_len = playerCtx->out_buffer_size;
// -------------------------------- 更新音频时间戳 ----------------------------------- //
if (pFrame->pts != AV_NOPTS_VALUE) {
playerCtx->audio_clk = av_q2d(playerCtx->audio_stream->time_base) * pFrame->pts; /* time_base 是一个分数 av_q2d是一个将分数转换成小数的函数。
在此处是用pts乘以time_base得到时间戳 */
}
playerCtx->audio_pts_duration = av_q2d(playerCtx->audio_stream->time_base) * pFrame->pkt_duration; // 更新音频帧的持续时间
av_frame_unref(pFrame);
}
// ------------------------------ end ------------------------------------- //
av_frame_free(&pFrame);
av_packet_unref(pkt);
av_packet_free(&pkt);
}
}
// sdl_audio_callback SDL 的系统回调函数
void sdl_audio_callback(void* userdata, Uint8* stream, int len) {
PlayerContext* playerCtx = (PlayerContext*)userdata;
if (!playerCtx || playerCtx->quit) {
return;
}
// 清空stream中的内存 SDL 2.0
SDL_memset(stream, 0, len);
if (playerCtx->audio_len == 0)
return;
// 尽可能的给系统传递音频buffer,但不会超出已经解析的大小(audio_len),也不会多给超出系统索要的大小(len)
Uint32 streamlen = ((Uint32)len > playerCtx->audio_len ? playerCtx->audio_len : len);
SDL_MixAudio(stream, playerCtx->audio_pos, streamlen, SDL_MIX_MAXVOLUME); // 给系统要分配的控件赋值
playerCtx->audio_pos += streamlen; // 音频缓存的位置向前
playerCtx->audio_len -= streamlen; // 音频缓存去掉已经分配掉的大小
}
これは、計算されたオーディオ時に復号オーディオフレームに注意すべきです。とにAUDIO_CLKに保存されています。これは、ビデオフレームが表示されたときの基礎になります。
playerCtx->audio_clk = av_q2d(playerCtx->audio_stream->time_base) * pFrame->pts;
現在のタイムスタンプはPTSが算出される(即ち、オーディオの最初の数フレーム)のタイムベースを乗じたtime_base
(グループは、その値は1秒/フレームレートのビデオに等しい時間の分数である)現在のタイムスタンプPTSを得ました。
デコードして、ビデオフレームを果たしています
同様のビデオフレームとオーディオの復号処理が、映像と音声を再生するの不足が完全に異なっています。ビデオ再生映像をレンダリングする必要があるため。しかし、また、ビデオフレームを表示するには、オーディオタイムスタンプに応じました。
// decode_video_thread 解码视频packet的线程
void decode_video_thread(PlayerContext* playerCtx) {
AVFrame* pFrame = nullptr;
while (playerCtx && !playerCtx->quit) {
AVPacket *pkt;
if (!playerCtx->video_queue.pull(pkt)) { // 从视频队列中取出视频包
SDL_Delay(1);
continue;
}
// ------------------------ 根据视频包解码出一个视频帧 ------------------------ //
if (avcodec_send_packet(playerCtx->videoCodecCtx, pkt)) {
exit(1);
return;
}
int ret = 0;
while(ret >= 0){
if (!pFrame) {
if (!(pFrame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
// 从编码器接收一个frame
ret = avcodec_receive_frame(playerCtx->videoCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
// --------------------------- 计算视频时间戳 ------------------------------ //
double pts = 0;
int64_t f_pts = pFrame->pts;
int64_t pkt_dts = pFrame->pkt_dts;
if (pkt_dts != AV_NOPTS_VALUE) {
pts = av_frame_get_best_effort_timestamp(pFrame); // 尽力获取视频帧的时间戳
}
else {
pts = pFrame->pts; // 如果获取时间戳失败,则将当前的pts作为视频的时间戳
}
pts *= av_q2d(playerCtx->video_stream->time_base);/* time_base 是一个分数 av_q2d是一个将分数转换成小数的函数。
在此处是用pts乘以time_base得到时间戳 */
if (pts == 0.0) {
pts = playerCtx->video_clk;
}
//计算视频流的时间基。av_q2d将一个分数转换成小数
double frame_delay = av_q2d(playerCtx->video_stream->time_base);
frame_delay += pFrame->repeat_pict * (frame_delay * 0.5); /* 计算视频延迟。 根据 pFrame->repeat_pict的注释:
extra_delay = repeat_pict / (2*fps) => extra_delay = repeat_pict * fps * 0.5 */
pts += frame_delay; // 视频帧的时间戳+视频延迟等于视频的实际时间戳
playerCtx->video_clk = pts; // 更新视频时间戳
// ---------------------------- 音视频同步 -------------------------- //
int64_t delay = (playerCtx->video_clk - (playerCtx->audio_clk + playerCtx->audio_pts_duration)) * 1000; // 计算视频时间戳和音频的差距。因为这里的单位是微妙。所以*1000 转换成毫秒
if (delay > 0) { // delay大于0说明视频时间戳提前了,等待一段时间再显示视频。
SDL_Delay(delay);
}
// ---------------------------- SDL 显示视频 ----------------------------//
sws_scale(playerCtx->vi_convert_ctx, pFrame->data, pFrame->linesize, 0 // 对视频数据进行缩放
, playerCtx->videoCodecCtx->height, playerCtx->pFrameYUV->data, playerCtx->pFrameYUV->linesize);
// 更细屏幕的纹理,参数:(texture纹理,rect区域nullptr为更新全局,plane像素数据,pitch数据大小)
SDL_UpdateYUVTexture(playerCtx->texture, nullptr,
playerCtx->pFrameYUV->data[0], playerCtx->pFrameYUV->linesize[0], //y
playerCtx->pFrameYUV->data[1], playerCtx->pFrameYUV->linesize[1], //u
playerCtx->pFrameYUV->data[2], playerCtx->pFrameYUV->linesize[2]); //v
//重置渲染器
SDL_RenderClear(playerCtx->renderer);
//将纹理信息拷贝给渲染器
SDL_RenderCopy(playerCtx->renderer, playerCtx->texture, nullptr, &playerCtx->sdlRect);
// 显示
SDL_RenderPresent(playerCtx->renderer);
// --------------------------- end --------------------------------- //
av_frame_unref(pFrame);
}
av_frame_free(&pFrame);
av_packet_unref(pkt);
av_packet_free(&pkt);
}
}
実際PTSを果たしていないかもしれPTSにおけるビデオフレームの異なるフォーマットに応じて、ビデオタイムスタンプを計算する際に(本明細書に関連するIフレームと、P フレーム、それによって一般的な問題は、我々は次の章、または自己百度に話します)av_frame_get_best_effort_timestamp
PTSは、ビデオフレームを取得します。ビデオストリームは今回イルPTSを乗じてtime_base
)(現在のビデオのタイムスタンプを与えます。
SDLのオーディオシステムが自動的にオーディオフレームレートを再生しているため。だから、彼のプレー率は一般的に正しいです。そして、私たちは、オーディオとビデオのタイムスタンプことを知っています。オーディオの時間枠によると、ラインがそれぞれデコードされたビデオフレーム後に待機するかどうかを感じることができます。
前二者の2つの例は、最も基本的なビデオやオーディオプレイヤーであると言うことができます。しかし、彼らはすべてのプレーヤーの必須プロセスが含まれています。次は、2人の完全なプレーヤーを書くかもしれません。ビデオ早送り付き。スピードとプレーの他の態様は、書面またはストリームを押して、約2を放送します。ご期待。