ffplayは、FFmpegプロジェクトに付属するシンプルなプレーヤーで、FFmpegが提供するデコーダーとSDLライブラリを使用してビデオを再生します。この記事は、FFmpegプロジェクトバージョン4.1の分析に基づいています。ffplayのソースコードリストは次のとおりです。https:
//github.com/FFmpeg/FFmpeg/blob/n4.1/fftools/ffplay.c
プレイコントロール
一時停止/続行
一時停止/再開状態の切り替えは、ユーザーがスペースバーを押すことで実現されます。スペースバーを押すたびに、一時停止/再開状態が1回反転します。
7.1.1状態切り替えの一時停止/続行
関数呼び出しの関係は次のとおりです。
main() -->
event_loop() -->
toggle_pause() -->
stream_toggle_pause()
stream_toggle_pause()は、状態のロールオーバーを実現します。
/* pause or resume the video */
static void stream_toggle_pause(VideoState *is)
{
if (is->paused) {
// 这里表示当前是暂停状态,将切换到继续播放状态。在继续播放之前,先将暂停期间流逝的时间加到frame_timer中
is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated;
if (is->read_pause_return != AVERROR(ENOSYS)) {
is->vidclk.paused = 0;
}
set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
}
set_clock(&is->extclk, get_clock(&is->extclk), is->extclk.serial);
is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused;
}
一時停止状態でのビデオ再生
video_refresh()関数には次のコードがあります。
/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
......
// 视频播放
if (is->video_st) {
......
// 暂停处理:不停播放上一帧图像
if (is->paused)
goto display;
......
}
......
}
一時停止状態では、画像の最後のフレーム(最後のフレーム)が実際に連続して再生されます。画面は更新されません。
フレームごとに再生
フレームごとの再生とは、ユーザーがsキーを押すたびに、プレーヤーが画像のフレームを再生することを意味します。
フレームごとの再生方法は、sキーを押すたびに状態が再生に切り替わり、1フレーム再生すると状態が一時停止に切り替わります。
関数呼び出しの関係は次のとおりです。
main() -->
event_loop() -->
step_to_next_frame() -->
stream_toggle_pause()
実装コードは、次のように比較的単純です。
static void step_to_next_frame(VideoState *is)
{
/* if the stream is paused unpause it, then step */
if (is->paused)
stream_toggle_pause(is); // 确保切换到播放状态,播放一帧画面
is->step = 1;
}
/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
......
// 视频播放
if (is->video_st) {
......
if (is->step && !is->paused)
stream_toggle_pause(is); // 逐帧播放模式下,播放一帧画面后暂停
......
}
......
}
シーク操作
SEEK操作は、再生プログレスバーをマウスでドラッグするなど、ユーザーの介入によって再生プログレスを変更する方法です。
データ構造とSEEKロゴ
関連するデータ変数は次のように定義されています。
typedef struct VideoState {
......
int seek_req; // 标识一次SEEK请求
int seek_flags; // SEEK标志,诸如AVSEEK_FLAG_BYTE等
int64_t seek_pos; // SEEK的目标位置(当前位置+增量)
int64_t seek_rel; // 本次SEEK的位置增量
......
} VideoState;
「VideoState.seek_flags」はSEEKフラグを表します。SEEKロゴのタイプは次のように定義されています。
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
SEEKターゲット再生ポイント(以下、SEEKポイントという)の決定は、異なるSEEKマークに応じて、以下の状況に分けられる。
- AVSEEK_FLAG_BYTE:SEEKポイントは、ファイル内の位置(バイト単位)に対応します。一部のデマルチプレクサは、この状況をサポートしていない場合があります。
- AVSEEK_FLAG_FRAME:SEEKポイントは、ストリーム内のフレームシリアル番号(?フレームシリアル番号またはフレーム
PTS?)に対応し、ストリームはstream_indexによって指定されます。一部のデマルチプレクサは、この状況をサポートしていない場合があります。 - 上記の2つのフラグが含まれておらず、stream_indexが有効な場合:SEEKポイントはタイムスタンプに対応し、単位はストリームのタイムベースであり、ストリームはstream_indexによって指定されます。SEEKポイントの値は、「ターゲットフレームのポイント(秒)xストリームのタイムベース」によって取得されます。
- 上記の2つのフラグが含まれておらず、stream_indexが-1の場合、SEEKポイントはタイムスタンプに対応し、単位はAV_TIME_BASEです。SEEKポイントの値は、「ターゲットフレームのポイント(秒)×AV_TIME_BASE」によって取得されます。
- AVSEEK_FLAG_ANY:SEEKポイントはフレーム番号(未定)に対応し、再生ポイントは任意のフレーム(非キーフレームを含む)にとどまることができます。一部のデマルチプレクサは、この状況をサポートしていない場合があります。
- AVSEEK_FLAG_BACKWARD:忽略。
ここで、AV_TIME_BASEは、FFmpegによって内部的に使用されるタイムベースであり、次のように定義されます。
/**
* Internal time base represented as integer
*/
#define AV_TIME_BASE 1000000
AV_TIME_BASEは1000000usを意味します。
SEEKトリガー方式
ユーザーが「PAGEUP」、「PAGEDOWN」、「UP」、「DOWN」、「LEFT」、「RHIGHT」ボタンを押し、マウスでプログレスバーをドラッグすると、再生の進行状況が変化し、SEEK操作がトリガーされます。 。
event_loop()関数によって実行されるSDLメッセージ処理には、次のコードスニペットがあります。
case SDLK_LEFT:
incr = seek_interval ? -seek_interval : -10.0;
goto do_seek;
case SDLK_RIGHT:
incr = seek_interval ? seek_interval : 10.0;
goto do_seek;
case SDLK_UP:
incr = 60.0;
goto do_seek;
case SDLK_DOWN:
incr = -60.0;
do_seek:
if (seek_by_bytes) {
pos = -1;
if (pos < 0 && cur_stream->video_stream >= 0)
pos = frame_queue_last_pos(&cur_stream->pictq);
if (pos < 0 && cur_stream->audio_stream >= 0)
pos = frame_queue_last_pos(&cur_stream->sampq);
if (pos < 0)
pos = avio_tell(cur_stream->ic->pb);
if (cur_stream->ic->bit_rate)
incr *= cur_stream->ic->bit_rate / 8.0;
else
incr *= 180000.0;
pos += incr;
stream_seek(cur_stream, pos, incr, 1);
} else {
pos = get_master_clock(cur_stream);
if (isnan(pos))
pos = (double)cur_stream->seek_pos / AV_TIME_BASE;
pos += incr;
if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE)
pos = cur_stream->ic->start_time / (double)AV_TIME_BASE;
stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);
}
break;
seek_by_bytesが有効な場合(AVSEEK_FLAG_BYTEフラグに対応)、SEEKポイントはファイル内の位置に対応し、上記のコードは1秒のデータに対応する再生増分を設定します。無効な場合、SEEKポイントは再生時間。当面、seek_by_bytesが有効になるとは考えていません。
この関数は、次の関数を実装します。
- まず、SEEK操作の再生進行増分(SEEK増分)とターゲット再生ポイント(SEEKポイント)を決定します。seek_by_bytesが有効でない場合は、増分を10.0秒(ユーザーが「RHIGHT」を押したとき)などの選択した値に設定します。キー)。
- 同期されたマスタークロックに進行状況の増分を追加して、SEEKポイントを取得します。後続のSEEK操作で使用するために、最初に関連する値を記録します。stream_seek(cur_stream、(int64_t)(pos * AV_TIME_BASE)、(int64_t)(incr * AV_TIME_BASE)、0);は、マイクロ秒単位の精度で、ターゲットの再生ポイントと再生進行状況の増分の2つのパラメーターを記録します。この関数を呼び出す前提は、セクション8.1のケース[4]のみを考慮することです。
単なる変数代入であるstream_seak()関数の実装を見てください。
/* seek in the stream */
static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes)
{
if (!is->seek_req) {
is->seek_pos = pos;
is->seek_rel = rel;
is->seek_flags &= ~AVSEEK_FLAG_BYTE;
if (seek_by_bytes)
is->seek_flags |= AVSEEK_FLAG_BYTE;
is->seek_req = 1;
SDL_CondSignal(is->continue_read_thread);
}
}
SEEK操作の実装
SEEK操作は、逆多重化スレッドのメインループで処理されます。
static int read_thread(void *arg)
{
......
for (;;) {
if (is->seek_req) {
int64_t seek_target = is->seek_pos;
int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
// FIXME the +-2 is due to rounding being not done in the correct direction in generation
// of the seek_pos/seek_rel variables
ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR,
"%s: error while seeking\n", is->ic->url);
} else {
if (is->audio_stream >= 0) {
packet_queue_flush(&is->audioq);
packet_queue_put(&is->audioq, &flush_pkt);
}
if (is->subtitle_stream >= 0) {
packet_queue_flush(&is->subtitleq);
packet_queue_put(&is->subtitleq, &flush_pkt);
}
if (is->video_stream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
if (is->seek_flags & AVSEEK_FLAG_BYTE) {
set_clock(&is->extclk, NAN, 0);
} else {
set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
}
}
is->seek_req = 0;
is->queue_attachments_req = 1;
is->eof = 0;
if (is->paused)
step_to_next_frame(is);
}
}
......
}
上記のコードのSEEK操作は、次の手順を実行します。
- avformat_seek_file()を呼び出して、デマルチプレクサでのSEEKポイント切り替え操作を完了します。
// 函数原型
int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
// 调用代码
ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
この関数は、SEEK操作が完了するのを待ってから戻ります。実際の再生ポイントは、パラメータtsに最も近くなるように努め、[min_ts、max_ts]の間隔内で、ts位置が正常に再生できない可能性があるため、再生ポイントが必ずしもts位置にあるとは限りません。
関数のSEEKポイントに関連する3つのパラメーター(実際のパラメーター「seek_min」、「seek_target」、「seek_max」)は、SEEKフラグ(実際のパラメーター「is-> seek_flags」)に関連しています。ここでは「is-> seek_flags」です。値は0で、これはセクション7.4.1のセクション[4]の状況に対応します。
- 各デコーダーバッファフレームをフラッシュして、現在の再生シーケンスでフレームの再生を完了してから、新しい再生シーケンスを開始します(再生シーケンスは、各データ構造の「シリアル」変数でマークされますが、ここでは展開されません)。コードは次のように表示されます。
if (is->video_stream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
- このSEEKリクエストフラグをクリアしますis-> seek_req = 0;
記事のソース:https://www.cnblogs.com/leisure_chn/p/10316225.html
C / C ++ Technology Exchange Group:[960994558]いくつかの優れた学習用書籍、大手企業からのインタビューの質問、および私が優れていると思う人気のある技術教育ビデオ資料をまとめました。必要に応じて追加できます。〜