【转】FFPLAY的原理(六)

【ffplay播放器】ffplay 播放器整体架构 文章目录前言ffplay 简介ffplay中的线程主线程解复用线程 read_thread视频解码线程 video_thread音频解码线程 audio_thread音频播放线程总结技术参考 前言 ffplay 是入门播放器的非常合适的一个开源项目,支持本地视频文件播放,也支持网络流的播放。通过对 ffplay 的学习可以知道一个播放器如何工作的。这篇文章先整体介绍一下 ffplay 播放器的整体架构。从整体上对播放器的设计有个大概的了解。ijkplayer播放器内核是基于ffplay开发的,所以掌握 f 阅读详情

显示视频

这就是我们的视频线程。现在我们看过了几乎所有的线程除了一个--记得我们调用schedule_refresh()函数吗?让我们看一下实际中是如何做的:

static void schedule_refresh(VideoState *is, int delay) {

SDL_AddTimer(delay, sdl_refresh_timer_cb, is);

}

函数SDL_AddTimer()是SDL中的一个定时(特定的毫秒)执行用户定义的回调函数(可以带一些参数user data)的简单函数。我们将用这个函数来定时刷新视频--每次我们调用这个函数的时候,它将设置一个定时器来触发定时事件来把一帧从图像队列中显示到屏 幕上。

但是,让我们先触发那个事件。

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {

SDL_Event event;

event.type = FF_REFRESH_EVENT;

event.user.data1 = opaque;

SDL_PushEvent(&event);

return 0;

}

这里向队列中写入了一个现在很熟悉的事件。FF_REFRESH_EVENT被定义成SDL_USEREVENT+1。要注意的一件事是当返回0的时候,SDL停止定时器,于是回调就不会再发生。

现在我们产生了一个FF_REFRESH_EVENT事件,我们需要在事件循环中处理它:

for(;;) {

SDL_WaitEvent(&event);

switch(event.type) {

case FF_REFRESH_EVENT:

video_refresh_timer(event.user.data1);

break;

于是我们就运行到了这个函数,在这个函数中会把数据从图像队列中取出:

void video_refresh_timer(void *userdata) {

VideoState *is = (VideoState *)userdata;

VideoPicture *vp;

if(is->video_st) {

if(is->pictq_size == 0) {

schedule_refresh(is, 1);

} else {

vp = &is->pictq[is->pictq_rindex];

schedule_refresh(is, 80);

video_display(is);

if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {

is->pictq_rindex = 0;

}

SDL_LockMutex(is->pictq_mutex);

is->pictq_size--;

SDL_CondSignal(is->pictq_cond);

SDL_UnlockMutex(is->pictq_mutex);

}

} else {

schedule_refresh(is, 100);

}

}

现在,这只是一个极其简单的函数:当队列中有数据的时候,他从其中获得数据,为下一帧设置定时器,调用video_display函数来真正显示图像到屏 幕上,然后把队列读索引值加1,并且把队列的尺寸size减1。你可能会注意到在这个函数中我们并没有真正对vp做一些实际的动作,原因是这样的:我们将 在后面处理。我们将在后面同步音频和视频的时候用它来访问时间信息。你会在这里看到这个注释信息“timing密码here”。那里我们将讨论什么时候显 示下一帧视频,然后把相应的值写入到schedule_refresh()函数中。现在我们只是随便写入一个值80。从技术上来讲,你可以猜测并验证这个 值,并且为每个电影重新编译程序,但是:1)过一段时间它会漂移;2)这种方式是很笨的。我们将在后面来讨论它。

我们几乎做完了;我们仅仅剩了最后一件事:显示视频!下面就是video_display函数:

void video_display(VideoState *is) {

SDL_Rect rect;

VideoPicture *vp;

AVPicture pict;

float aspect_ratio;

int w, h, x, y;

int i;

vp = &is->pictq[is->pictq_rindex];

if(vp->bmp) {

if(is->video_st->codec->sample_aspect_ratio.num == 0) {

aspect_ratio = 0;

} else {

aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio) *

is->video_st->codec->width / is->video_st->codec->height;

}

if(aspect_ratio <= 0.0) {

aspect_ratio = (float)is->video_st->codec->width /

(float)is->video_st->codec->height;

}

h = screen->h;

w = ((int)rint(h * aspect_ratio)) & -3;

if(w > screen->w) {

w = screen->w;

h = ((int)rint(w / aspect_ratio)) & -3;

}

x = (screen->w - w) / 2;

y = (screen->h - h) / 2;

rect.x = x;

rect.y = y;

rect.w = w;

rect.h = h;

SDL_DisplayYUVOverlay(vp->bmp, &rect);

}

}

因为我们的屏幕可以是任意尺寸(我们设置为640x480并且用户可以自己来改变尺寸),我们需要动态计算出我们显示的图像的矩形大小。所以一开始我们需 要计算出电影的纵横比aspect ratio,表示方式为宽度除以高度。某些编解码器会有奇数采样纵横比,只是简单表示了一个像素或者一个采样的宽度除以高度的比例。因为宽度和高度在我们 的编解码器中是用像素为单位的,所以实际的纵横比与纵横比乘以样本纵横比相同。某些编解码器会显示纵横比为0,这表示每个像素的纵横比为1x1。然后我们 把电影缩放到适合屏幕的尽可能大的尺寸。这里的& -3表示与-3做与运算,实际上是让它们4字节对齐。然后我们把电影移到中心位置,接着调用SDL_DisplayYUVOverlay()函数。

结果是什么?我们做完了吗?嗯,我们仍然要重新改写声音部分的代码来使用新的VideoStruct结构体,但是那些只是尝试着改变,你可以看一下那些参考示例代码。最后我们要做的是改变ffmpeg提供的默认退出回调函数为我们的退出回调函数。

VideoState *global_video_state;

int decode_interrupt_cb(void) {

return (global_video_state && global_video_state->quit);

}

我们在主函数中为大结构体设置了global_video_state。

这就是了!让我们编译它:

gcc -o tutorial04 tutorial04.c -lavutil -lavformat -lavcodec -lz -lm /

`sdl-config --cflags --libs`

请享受一下没有经过同步的电影!下次我们将编译一个可以最终工作的电影播放器。

如何同步视频

PTS和DTS

幸运的是,音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面。音频流有采样,视频流有每秒的帧率。然而,如果我们只是简单的通过数帧 和乘以帧率的方式来同步视频,那么就很有可能会失去同步。于是作为一种补充,在流中的包有种叫做DTS(解码时间戳)和PTS(显示时间戳)的机制。为了 这两个参数,你需要了解电影存放的方式。像MPEG等格式,使用被叫做B帧(B表示双向bidrectional)的方式。另外两种帧被叫做I帧和P帧 (I表示关键帧,P表示预测帧)。I帧包含了某个特定的完整图像。P帧依赖于前面的I帧和P帧并且使用比较或者差分的方式来编码。B帧与P帧有点类似,但 是它是依赖于前面和后面的帧的信息的。这也就解释了为什么我们可能在调用avcodec_decode_video以后会得不到一帧图像。

所以对于一个电影,帧是这样来显示的:I B B P。现在我们需要在显示B帧之前知道P帧中的信息。因此,帧可能会按照这样的方式来存储:IPBB。这就是为什么我们会有一个解码时间戳和一个显示时间戳 的原因。解码时间戳告诉我们什么时候需要解码,显示时间戳告诉我们什么时候需要显示。所以,在这种情况下,我们的流可以是这样的:

PTS: 1 4 2 3

DTS: 1 2 3 4

Stream: I P B B

通常PTS和DTS只有在流中有B帧的时候会不同。

当我们调用av_read_frame()得到一个包的时候,PTS和DTS的信息也会保存在包中。但是我们真正想要的PTS是我们刚刚解码出来的原始帧 的PTS,这样我们才能知道什么时候来显示它。然而,我们从avcodec_decode_video()函数中得到的帧只是一个AVFrame,其中并 没有包含有用的PTS值(注意:AVFrame并没有包含时间戳信息,但当我们等到帧的时候并不是我们想要的样子)。然而,ffmpeg重新排序包以便于 被avcodec_decode_video()函数处理的包的DTS可以总是与其返回的PTS相同。但是,另外的一个警告是:我们也并不是总能得到这个 信息。

不用担心,因为有另外一种办法可以找到帧的PTS,我们可以让程序自己来重新排序包。我们保存一帧的第一个包的PTS:这将作为整个这一帧的 PTS。我们 可以通过函数avcodec_decode_video()来计算出哪个包是一帧的第一个包。怎样实现呢?任何时候当一个包开始一帧的时 候,avcodec_decode_video()将调用一个函数来为一帧申请一个缓冲。当然,ffmpeg允许我们重新定义那个分配内存的函数。所以我 们制作了一个新的函数来保存一个包的时间戳。

当然,尽管那样,我们可能还是得不到一个正确的时间戳。我们将在后面处理这个问题。

同步

现在,知道了什么时候来显示一个视频帧真好,但是我们怎样来实际操作呢?这里有个主意:当我们显示了一帧以后,我们计算出下一帧显示的时间。然后我们简单 的设置一个新的定时器来。你可能会想,我们检查下一帧的PTS值而不是系统时钟来看超时是否会到。这种方式可以工作,但是有两种情况要处理。

首先,要知道下一个PTS是什么。现在我们能添加视频速率到我们的PTS中--太对了!然而,有些电影需要帧重复。这意味着我们重复播放当前的帧。这将导致程序显示下一帧太快了。所以我们需要计算它们。

第二,正如程序现在这样,视频和音频播放很欢快,一点也不受同步的影响。如果一切都工作得很好的话,我们不必担心。但是,你的电脑并不是最好的,很多视频 文件也不是完好的。所以,我们有三种选择:同步音频到视频,同步视频到音频,或者都同步到外部时钟(例如你的电脑时钟)。从现在开始,我们将同步视频到音 频。

写代码:获得帧的时间戳

现在让我们到代码中来做这些事情。我们将需要为我们的大结构体添加一些成员,但是我们会根据需要来做。首先,让我们看一下视频线程。记住,在这里我们得到 了解码线程输出到队列中的包。这里我们需要的是从avcodec_decode_video函数中得到帧的时间戳。我们讨论的第一种方式是从上次处理的包 中得到DTS,这是很容易的:

double pts;

for(;;) {

if(packet_queue_get(&is->videoq, packet, 1) < 0) {

// means we quit getting packets

break;

}

pts = 0;

// Decode video frame

len1 = avcodec_decode_video(is->video_st->codec,

pFrame, &frameFinished,

packet->data, packet->size);

if(packet->dts != AV_NOPTS_VALUE) {

pts = packet->dts;

} else {

pts = 0;

}

pts *= av_q2d(is->video_st->time_base);

如果我们得不到PTS就把它设置为0。

好,那是很容易的。但是我们所说的如果包的DTS不能帮到我们,我们需要使用这一帧的第一个包的PTS。我们通过让ffmpeg使用我们自己的申请帧程序来实现。下面的是函数的格式:

int get_buffer(struct AVCodecContext *c, AVFrame *pic);

void release_buffer(struct AVCodecContext *c, AVFrame *pic);

申请函数没有告诉我们关于包的任何事情,所以我们要自己每次在得到一个包的时候把PTS保存到一个全局变量中去。我们自己以读到它。然后,我们把值保存到AVFrame结构体难理解的变量中去。所以一开始,这就是我们的函数:

uint64_t global_video_pkt_pts = AV_NOPTS_VALUE;

int our_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;

}

void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) {

if(pic) av_freep(&pic->opaque);

avcodec_default_release_buffer(c, pic);

}

函数avcodec_default_get_buffer和avcodec_default_release_buffer是ffmpeg中默认的申请缓冲的函数。函数av_freep是一个内存管理函数,它不但把内存释放而且把指针设置为NULL。

现在到了我们流打开的函数(stream_component_open),我们添加这几行来告诉ffmpeg如何去做:

codecCtx->get_buffer = our_get_buffer;

codecCtx->release_buffer = our_release_buffer;

现在我们必需添加代码来保存PTS到全局变量中,然后在需要的时候来使用它。我们的代码现在看起来应该是这样子:

for(;;) {

if(packet_queue_get(&is->videoq, packet, 1) < 0) {

// means we quit getting packets

break;

}

pts = 0;

// Save global pts to be stored in pFrame in first call

global_video_pkt_pts = packet->pts;

// Decode video frame

len1 = avcodec_decode_video(is->video_st->codec, pFrame, &frameFinished,

packet->data, packet->size);

if(packet->dts == AV_NOPTS_VALUE

&& pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) {

pts = *(uint64_t *)pFrame->opaque;

} else if(packet->dts != AV_NOPTS_VALUE) {

pts = packet->dts;

} else {

pts = 0;

}

pts *= av_q2d(is->video_st->time_base);

技术提示:你可能已经注意到我们使用int64来表示PTS。这是因为PTS是以整型来保存的。这个值是一个时间戳相当于时间的度量,用来以流的 time_base为单位进行时间度量。例如,如果一个流是24帧每秒,值为42的PTS表示这一帧应该排在第42个帧的位置如果我们每秒有24帧(这里 并不完全正确)。

我们可以通过除以帧率来把这个值转化为秒。流中的time_base值表示1/framerate(对于固定帧率来说),所以得到了以秒为单位的PTS,我们需要乘以time_base。

写代码:使用PTS来同步

现在我们得到了PTS。我们要注意前面讨论到的两个同步问题。我们将定义一个函数叫做synchronize_video,它可以更新同步的 PTS。这个函数也能最终处理我们得不到PTS的情况。同时我们要知道下一帧的时间以便于正确设置刷新速率。我们可以使用内部的反映当前视频已经播放时间 的时钟 video_clock来完成这个功能。我们把这些值添加到大结构体中。

typedef struct VideoState {

double video_clock; ///

下面的是函数synchronize_video,它可以很好的自我注释:

double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {

double frame_delay;

if(pts != 0) {

is->video_clock = pts;

} else {

pts = is->video_clock;

}

frame_delay = av_q2d(is->video_st->codec->time_base);

frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);

is->video_clock += frame_delay;

return pts;

}

你也会注意到我们也计算了重复的帧。

现在让我们得到正确的PTS并且使用queue_picture来队列化帧,添加一个新的时间戳参数pts:

// Did we get a video frame?

if(frameFinished) {

pts = synchronize_video(is, pFrame, pts);

if(queue_picture(is, pFrame, pts) < 0) {

break;

}

}

对于queue_picture来说唯一改变的事情就是我们把时间戳值pts保存到VideoPicture结构体中,我们必需添加一个时间戳变量到结构体中并且添加一行代码:

typedef struct VideoPicture {

...

double pts;

}

int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {

... stuff ...

if(vp->bmp) {

... convert picture ...

vp->pts = pts;

... alert queue ...

}

现在我们的图像队列中的所有图像都有了正确的时间戳值,所以让我们看一下视频刷新函数。你会记得上次我们用80ms的刷新时间来欺骗它。那么,现在我们将会算出实际的值。

我们的策略是通过简单计算前一帧和现在这一帧的时间戳来预测出下一个时间戳的时间。同时,我们需要同步视频到音频。我们将设置一个音频时间 audio clock;一个内部值记录了我们正在播放的音频的位置。就像从任意的mp3播放器中读出来的数字一样。既然我们把视频同步到音频,视频线程使用这个值来 算出是否太快还是太慢。

我们将在后面来实现这些代码;现在我们假设我们已经有一个可以给我们音频时间的函数get_audio_clock。一旦我们有了这个值,我们在音频和视 频失去同步的时候应该做些什么呢?简单而有点笨的办法是试着用跳过正确帧或者其它的方式来解决。作为一种替代的手段,我们会调整下次刷新的值;如果时间戳 太落后于音频时间,我们加倍计算延迟。如果时间戳太领先于音频时间,我们将尽可能快的刷新。既然我们有了调整过的时间和延迟,我们将把它和我们通过 frame_timer计算出来的时间进行比较。这个帧时间frame_timer将会统计出电影播放中所有的延时。换句话说,这个 frame_timer就是指我们什么时候来显示下一帧。我们简单的添加新的帧定时器延时,把它和电脑的系统时间进行比较,然后使用那个值来调度下一次刷 新。这可能有点难以理解,所以请认真研究代码:

void video_refresh_timer(void *userdata) {

VideoState *is = (VideoState *)userdata;

VideoPicture *vp;

double actual_delay, delay, sync_threshold, ref_clock, diff;

if(is->video_st) {

if(is->pictq_size == 0) {

schedule_refresh(is, 1);

} else {

vp = &is->pictq[is->pictq_rindex];

delay = vp->pts - is->frame_last_pts;

if(delay <= 0 || delay >= 1.0) {

delay = is->frame_last_delay;

}

is->frame_last_delay = delay;

is->frame_last_pts = vp->pts;

ref_clock = get_audio_clock(is);

diff = vp->pts - ref_clock;

sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;

if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

if(diff <= -sync_threshold) {

delay = 0;

} else if(diff >= sync_threshold) {

delay = 2 * delay;

}

}

is->frame_timer += delay;

actual_delay = is->frame_timer - (av_gettime() / 1000000.0);

if(actual_delay < 0.010) {

actual_delay = 0.010;

}

schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));

video_display(is);

if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {

is->pictq_rindex = 0;

}

SDL_LockMutex(is->pictq_mutex);

is->pictq_size--;

SDL_CondSignal(is->pictq_cond);

SDL_UnlockMutex(is->pictq_mutex);

}

} else {

schedule_refresh(is, 100);

}

}

我们在这里做了很多检查:首先,我们保证现在的时间戳和上一个时间戳之间的处以delay是有意义的。如果不是的话,我们就猜测着用上次的延迟。接着,我 们有一个同步阈值,因为在同步的时候事情并不总是那么完美的。在ffplay中使用0.01作为它的值。我们也保证阈值不会比时间戳之间的间隔短。最后, 我们把最小的刷新值设置为10毫秒。

(这句不知道应该放在哪里)事实上这里我们应该跳过这一帧,但是我们不想为此而烦恼。

我们给大结构体添加了很多的变量,所以不要忘记检查一下代码。同时也不要忘记在函数streame_component_open中初始化帧时间frame_timer和前面的帧延迟frame delay:

is->frame_timer = (double)av_gettime() / 1000000.0;

is->frame_last_delay = 40e-3;

同步:声音时钟

现在让我们看一下怎样来得到声音时钟。我们可以在声音解码函数audio_decode_frame中更新时钟时间。现在,请记住我们并不是每次调用这个 函数的时候都在处理新的包,所以有我们要在两个地方更新时钟。第一个地方是我们得到新的包的时候:我们简单的设置声音时钟为这个包的时间戳。然后,如果一 个包里有许多帧,我们通过样本数和采样率来计算,所以当我们得到包的时候:

if(pkt->pts != AV_NOPTS_VALUE) {

is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;

}

然后当我们处理这个包的时候:

pts = is->audio_clock;

*pts_ptr = pts;

n = 2 * is->audio_st->codec->channels;

is->audio_clock += (double)data_size /

(double)(n * is->audio_st->codec->sample_rate);

一点细节:临时函数被改成包含pts_ptr,所以要保证你已经改了那些。这时的pts_ptr是一个用来通知audio_callback函数当前声音包的时间戳的指针。这将在下次用来同步声音和视频。

现在我们可以最后来实现我们的get_audio_clock函数。它并不像得到is->audio_clock值那样简单。注意我们会在每次处理 它的时候设置声音时间戳,但是如果你看了audio_callback函数,它花费了时间来把数据从声音包中移到我们的输出缓冲区中。这意味着我们声音时 钟中记录的时间比实际的要早太多。所以我们必须要检查一下我们还有多少没有写入。下面是完整的代码:

double get_audio_clock(VideoState *is) {

double pts;

int hw_buf_size, bytes_per_sec, n;

pts = is->audio_clock;

hw_buf_size = is->audio_buf_size - is->audio_buf_index;

bytes_per_sec = 0;

n = is->audio_st->codec->channels * 2;

if(is->audio_st) {

bytes_per_sec = is->audio_st->codec->sample_rate * n;

}

if(bytes_per_sec) {

pts -= (double)hw_buf_size / bytes_per_sec;

}

return pts;

}

你应该知道为什么这个函数可以正常工作了;)

这就是了!让我们编译它:

gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -lz -lm`sdl-config --cflags --libs`

最后,你可以使用我们自己的电影播放器来看电影了。下次我们将看一下声音同步,然后接下来的指导我们会讨论查询。

 
ffplay播放器研究分析 PacketQueue设计思路:设计⼀个多线程安全的队列引⼊serial的概念,区别前后数据包是否连续,主要应⽤于seek操作。设计了两类特殊的packet——flush_pkt和nullpkt(类似⽤于多线程编程的事件模型——往队列中放⼊ flush事件、放⼊null事件),我们在⾳频输出、视频输出、播放控制等模块时也会继续对flush_pkt和 nullpkt的作⽤展开分析。创建视频图像缩放上下文的函数。第⼀参数可以传NULL,默认会开辟⼀块新的空间。 阅读详情

相关推荐

ffplay源码分析1-概述

本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10301215.html ffplay是一个很简单的播放器,但是初次接触仍会感到概念和细节相当繁多,分析并不容易。深入理解一项技术需要足够的时间和大量的实践,由浅入深逐步迭代,没有时间就成了最大难题。本次分析过程断断续续持续了挺久,先是边读代码边加注释,后面才整理了笔记,再加上理解浅薄很难...

叶余的博客 1441

【FFmpeg】ffplay 命令行参数 ④ ( 修改窗口标题 -window_title 参数 | 设置播放循环次数 | 设置显示模式 -showmode 参数 | 设置视频滤镜 -vf 参数 )

一、ffplay 命令行参数 - 修改窗口标题 1、修改窗口标题 -window_title 参数 二、ffplay 命令行参数 - 设置播放次数 1、设置播放次数 -loop 参数 2、设置 播放 指定视频片段 循环播放次数 三、ffplay 命令行参数 - 设置显示模式 1、设置显示模式 -showmode 参数 2、显示视频 3、显示波形 3、显示频谱 四、ffplay 命令行参数 - 设置视频滤镜 1、设置视频滤镜 -vf 参数 2、常见的视频滤镜参数 3、单个视频滤镜示例 4、多个视频滤镜示例

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 8753

ffplay播放器原理学习

ffplay 播放器从原理上来讲和 windows directshow 差不多,只是没有使用 driectshow 那些名词术语来表述。从 directshow 的视角来看 ffplay 播放器,简单的划分一下模块和各个模块的文件,有些文件可能在多个模块中都有用到,只能不严格的划分。 Source filter Source filter 读文件模块,可以简单的分为 3 层,最底层的是 file,pipe,tcp,udp,http 等这些具体的本地文件或网络协议(注意 ffplay 把 file 也当协议

道亦无名 603

【音视频流媒体】 3、ffmpeg、ffplay、ffprobe 超详细介绍

ffmpeg 超详细介绍

呆呆的猫的博客 5603

FFmpeg之编译ffplay(十四)

问题: 若要ffmpeg源码编译出ffplay,需要先编译出SDL,安装到系统后,ffmpeg源码才能编译出ffplay. 解决: 一、编译SDL # wget http://libsdl.org/release/SDL2-2.0.14.tar.gz # tar zxvf SDL2-2.0.14.tar.gz # cd SDL2-2.0.14 # mkdir build # cd build # cmake .. # make -j4 # sudo make install 二、编译ffmpeg.

Android系统攻城狮 1876

寒假实训笔记day01——认识M4单片机之STM32F405RGT6

认识M4单片机之STM32F405RGT6(含义、规格、参数)

weixin_47200070的博客 8550

FFmpeg命令行之ffplay

FFmpeg命令行之ffplay

m0_60259116的博客 1786

DSP28335 ADC模块实战:从寄存器配置到精准数据采集(附代码详解)

本文详细解析DSP28335的ADC模块配置与数据采集实战,涵盖寄存器设置、采样窗口优化、中断处理及代码实现。重点解决ADC精度问题,如采样窗口不足导致数据偏差,提供完整代码示例和调试技巧,助力工程师实现精准数据采集。

kkk56的博客 862

FFplay源码分析-read_thread

FFplay源码分析-read_thread 本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 FFplay 源码分析系列以一条简单的命令开始,ffplay -i a.mp4。a.mp4下载链接:百度网盘,提取码:nl0s 。 如下图所示,本文主要讲解 read_thread() 函数的内部逻辑。这个流程图是根据上面的命令ffplay -i a.mp4 画的,有些流程我省略了,因为不会执行某些代码,所以有些if判断我没画出来,为了简洁。 从上面的流程图可以看出, r

u012117034的博客 3346

Android双目摄像头(RGB+IR)实时预览与人脸活体检测实战指南

本文详细介绍了Android双目摄像头(RGB+IR)实时预览与人脸活体检测的实战指南。通过双摄同步预览、人脸检测与活体判断等技术实现,提升安全认证的准确性和鲁棒性。文章还提供了开发环境搭建、常见问题解决方案及性能优化建议,帮助开发者快速掌握双目摄像头在Android平台的应用。

vulkan6gpu的博客 687

VINS-Fusion实战:从零搭建无人机视觉惯性导航系统(附ROS配置指南)

本文提供了一份详尽的VINS-Fusion实战指南,涵盖从硬件选型、传感器标定到ROS节点配置与参数调优的全过程。重点解析了如何将VINS-Fusion视觉惯性里程计系统与PX4飞控深度集成,并实现室外GPS融合,旨在帮助开发者从零搭建稳定、高精度的无人机视觉惯性导航系统。

lll78的博客 863

FFmpeg学习笔记--FFplay播放控制、FFplay基本命令参数、FFplay高级命令参数

left键(←)后退10秒,right键(→)前进10秒;:down键(↓)后退60秒,up键(↑)前进60秒;9 自定义左右键拖动间隔,以秒为基本单位,默认为10秒。6 强制使用解码器进行音频解码、视频解码和字幕解码。2 设置帧尺寸,仅适用于没有包含帧大小的视频。10 关闭图形化显示窗口,不显示视频。14 设置窗口标题,默认为输入文件名。16 设置显示模式,默认为0显示视频。1 强制显示宽高,用于调整窗口大小。7 设置播放视频的长度,即持续时间。17 设置视频滤镜和音频滤镜。

牵一只蜗牛去散步 7998

【WRF-UCM】BEP+BEM 模拟:README

【WRF-UCM】BEP+BEM 模拟:README

WW、forever的博客 222

ffplay播放器移植VC的工程:ffplay for MFC

ffplay播放器移植VC的工程:ffplayfor MFC本文介绍一个自己做的FFPLAY移植到VC下的开源工程:ffplayfor MFC。本工程将ffmpeg项目中的ffplay播放器(ffplay.c)移植到了VC的环境下。并且使用MFC做了一套简单的界面。它可以完成一个播放器播放视频的基本流程:解协议,解封装,视频/音频解码,视音频同步,视音频输出。此外还包含一些控制功能:播放,暂停/继

雷霄骅(leixiaohua1020)的专栏 3万+

从零开始做对容量规划:一文讲透 SAP Greenfield sizing 与 Quick Sizer 落地方法

摘要 SAP项目早期硬件规划面临业务细节缺失的挑战,Greenfield sizing方法通过Quick Sizer工具将业务需求转化为硬件资源需求。该方法聚焦主要负载驱动因素而非精确预测,采用标准化问卷形式输入并发用户数、业务处理量等参数,输出内存、CPU等资源需求。关键步骤包括选择正确的Quick Sizer版本、创建sizing项目、参考SAP基准测试和认证目录校准结果,最终将需求交由硬件厂商落地。该方法特别适用于缺乏历史数据的新系统部署,帮助在不确定性中建立可控的容量规划模型,避免资源浪费或性能不足

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 211

Debezium系列之:使用指定值替代指定字段的值

Debezium系列之:使用指定值替代指定字段的值

zhengzaifeidelushang的博客 306

ffplay播放器原理剖析

详细分析ffplay的基本框架及工作机制。

RonnieLiGe的专栏 3103

YOLOv11改进 | Mamba-YOLOv11-L:基于SSM的高效全局建模,精度与效率的双重突破

摘要: 本文提出Mamba-YOLOv11-L,通过集成状态空间模型(SSM)改进YOLOv11的全局建模能力。针对YOLOv11在复杂场景中全局依赖缺失、精度-效率矛盾等问题,SSM以$O(N)$计算复杂度实现长程语义捕捉。改进后的模型在工业缺陷检测(Recall↑12%)、边缘设备部署(内存↓16%)、多尺度目标检测(小目标mAP↑9%)等场景表现优异,推理速度保持>40 FPS。核心创新包括轻量化SSM模块设计(稀疏状态转移+局部上下文融合)及与YOLOv11骨干的深度集成,代码实现展示了SSM

走向CTO的路上... 566

ffplay视频播放原理分析

本文从整体播放流程出发,介绍了 ffplay 播放器播放媒体文件的主要流程,不深陷于代码细节。

lihui49的博客 1229

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

ffplay播放器

1 ffplay.c的意义 ffplay.c是FFmpeg源码⾃带的播放器,调⽤FFmpeg和SDL API实现⼀个⾮常有⽤的播放器。 例如哔哩哔哩著名开源项⽬ijkplayer也是基于ffplay.c进⾏⼆次开发。 ffplay实现了播放器的主体功能,掌握其原理对于我们独⽴开发播放器⾮常有帮助。 2 FFplay框架分析 播放器初始化 初始化packet queue 初始化frame queue 初始化clock 创建数据读取线程 线程的划分 数据读取线程 打开媒体⽂件 打开对应码流的decod

李点点 1896

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

音频和视频同步ffplay5原理

[转]音频和视频同步ffplay5原理(网上资料加个人理解,欢迎跟帖探讨) PTS和DTS 接触FFMPEG应用程序时间不长,一共8个tutorial,现在看到了第5个,花的时间最长,理解也是最难的。里面首先把电影文件分为audio和video,其中每个packet

tangchao198507的专栏 733

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

ffplay.c源码阅读之音频、字幕、视频渲染原理

ffplay.c源码阅读之音频、字幕、视频渲染原理

m0_60259116的博客 815

ffplay实现原理

## ffmpeg是音视频界的瑞士军刀: 1. 它提供了从录制、编码、封装、推流到拉流、解封装、解码、滤镜、播放的完整解决方案。 2. 它是跨平台的解决方案,一套代码适配Windows、Linux、Mac OS、iOS、Andriod。 3. 它原生支持hls、dash、rtmp、rtsp、http、tcp等主流协议,并提供统一的扩展方式方便扩展自己的协议,比如tutk、webrtc等,目前已经有开源库metaRTC可通过扩展集成到ffmpeg中实现webrtc的推拉流。 4. 它支持或插件支持所有封装格式

fsmpeg的博客 671

ffplay实现功能大全

ffplay 是一个基于 SDL 和 FFmpeg 的简单播放器,支持多种播放控制功能。通过键盘事件,用户可以实现播放、暂停、静音、音量调节、全屏切换、逐帧播放等操作。播放器启动时自动播放,暂停时通过 p 或空格键恢复。暂停状态下,视频停止解码并停留在最后一帧,音频则通过填充静音数据或停止解码来处理。时钟同步机制确保暂停和恢复时的音视频同步,暂停时时钟停止更新,恢复时通过计算暂停时间差来修正时钟。数据读取线程在暂停时停止读取新数据包,恢复时重新唤醒解码线程继续处理数据。

m0_73359068的博客 1147

ffplay 外部时钟同步的原理

概述 ffplay有三种同步方式:1、音频作为主时钟;2、视频作为主时钟;3、外部时钟源作为主时钟。默认的同步方式为“音频作为主时钟”方式,具体代码分析可查阅:https://blog.youkuaiyun.com/lyy901135/article/details/95307111 音频作为主时钟时,视频pts向音频的pts看齐,若视频播放延迟,则需进行丢帧处理。视频作为主时钟时,音频需向视频的pts看齐,...

lyy901135的博客 1886

ffplay音视频同步分析

double pts;// 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧//两时间差值,可以理解为持续时间// 最后一次更新的系统时钟// 时钟速度控制,用于控制播放速度int serial;int paused;// = 1 说明是暂停状态} Clock;audio:视频同步到⾳频。上⼀节中的A被触发,video输出需要作同步,同步的参考 (get_master_clock)是audclk.video:⾳频同步到视频。

weixin_50873490的博客 1317
上一篇: 【转】FFPLAY的原理(五)
下一篇: 【转】FFPLAY的原理(七)
code09
博客等级 码龄18年 31粉丝 56原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值