【转】ffmpeg tutorial 4:创建线程

FFmpeg基础知识总结 1. 概述从事多媒体技术研发工程师想必提到最多的开源工程即是ffmpeg,全称是Fast Forward MPEG,有很多视频相关的互联网公司都使用该开源程序,如Google、Facebook、Youtube、腾讯、优酷土豆、爱奇艺、暴风等等。但根据使用场景的不同,术语经常有不同的含义,极易造成沟通不一致。 阅读详情
 

Spawning Threads

Overview

Last time we added audio support by taking advantage of SDL's audio functions. SDL started a thread that made callbacks to a function we defined every time it needed audio. Now we're going to do the same sort of thing with the video display. This makes the code more modular and easier to work with - especially when we want to add syncing. So where do we start?

First we notice that our main function is handling an awful lot: it's running through the event loop, reading in packets, and decoding the video. So what we're going to do is split all those apart: we're going to have a thread that will be responsible for decoding the packets; these packets will then be added to the queue and read by the corresponding audio and video threads. The audio thread we have already set up the way we want it; the video thread will be a little more complicated since we have to display the video ourselves. We will add the actual display code to the main loop. But instead of just displaying video every time we loop, we will integrate the video display into the event loop. The idea is to decode the video, save the resulting frame in another queue, then create a custom event (FF_REFRESH_EVENT ) that we add to the event system, then when our event loop sees this event, it will display the next frame in the queue. Here's a handy ASCII art illustration of what is going on:

________ audio  _______      _____
| | pkts | | | | to spkr
| DECODE |----->| AUDIO |--->| SDL |-->
|________| |_______| |_____|
| video _______
| pkts | |
+---------->| VIDEO |
________ |_______| _______
| | | | |
| EVENT | +------>| VIDEO | to mon.
| LOOP |----------------->| DISP. |-->
|_______|<---FF_REFRESH----|_______|
The main purpose of moving controlling the video display via the event loop is that using an SDL_Delay thread, we can control exactly when the next video frame shows up on the screen. When we finally sync the video in the next tutorial, it will be a simple matter to add the code that will schedule the next video refresh so the right picture is being shown on the screen at the right time.

Simplifying Code

We're also going to clean up the code a bit. We have all this audio and video codec information, and we're going to be adding queues and buffers and who knows what else. All this stuff is for one logical unit, viz. the movie. So we're going to make a large struct that will hold all that information called the VideoState .

typedef struct VideoState {

AVFormatContext *pFormatCtx;
int videoStream, audioStream;
AVStream *audio_st;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
AVStream *video_st;
PacketQueue videoq;

VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;

SDL_Thread *parse_tid;
SDL_Thread *video_tid;

char filename[1024];
int quit;
} VideoState;
Here we see a glimpse of what we're going to get to. First we see the basic information - the format context and the indices of the audio and video stream, and the corresponding AVStream objects. Then we can see that we've moved some of those audio buffers into this structure. These (audio_buf, audio_buf_size, etc.) were all for information about audio that was still lying around (or the lack thereof). We've added another queue for the video, and a buffer (which will be used as a queue; we don't need any fancy queueing stuff for this) for the decoded frames (saved as an overlay). The VideoPicture struct is of our own creations (we'll see what's in it when we come to it). We also notice that we've allocated pointers for the two extra threads we will create, and the quit flag and the filename of the movie.

So now we take it all the way back to the main function to see how this changes our program. Let's set up our VideoState struct:

int main(int argc, char *argv[]) {

SDL_Event event;

VideoState *is;

is = av_mallocz (sizeof(VideoState));
av_mallocz () is a nice function that will allocate memory for us and zero it out.

Then we'll initialize our locks for the display buffer (pictq ), because since the event loop calls our display function - the display function, remember, will be pulling pre-decoded frames from pictq . At the same time, our video decoder will be putting information into it - we don't know who will get there first. Hopefully you recognize that this is a classic race condition . So we allocate it now before we start any threads. Let's also copy the filename of our movie into our VideoState .

pstrcpy(is->filename, sizeof(is->filename), argv[1]);

is->pictq_mutex = SDL_CreateMutex ();
is->pictq_cond = SDL_CreateCond ();
pstrcpy is a function from ffmpeg that does some extra bounds checking beyond strncpy.

Our First Thread

Now let's finally launch our threads and get the real work done:

schedule_refresh(is, 40);

is->parse_tid = SDL_CreateThread (decode_thread, is);
if(!is->parse_tid) {
av_free (is);
return -1;
}
schedule_refresh is a function we will define later. What it basically does is tell the system to push a FF_REFRESH_EVENT after the specified number of milliseconds. This will in turn call the video refresh function when we see it in the event queue. But for now, let's look at SDL_CreateThread () .

SDL_CreateThread () does just that - it spawns a new thread that has complete access to all the memory of the original process, and starts the thread running on the function we give it. It will also pass that function user-defined data. In this case, we're calling decode_thread() and with our VideoState struct attached. The first half of the function has nothing new; it simply does the work of opening the file and finding the index of the audio and video streams. The only thing we do different is save the format context in our big struct. After we've found our stream indices, we call another function that we will define, stream_component_open() . This is a pretty natural way to split things up, and since we do a lot of similar things to set up the video and audio codec, we reuse some code by making this a function.

The stream_component_open() function is where we will find our codec decoder, set up our audio options, save important information to our big struct, and launch our audio and video threads. This is where we would also insert other options, such as forcing the codec instead of autodetecting it and so forth. Here it is:

int stream_component_open(VideoState *is, int stream_index) {

AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx;
AVCodec *codec;
SDL_AudioSpec wanted_spec, spec;

if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}

// Get a pointer to the codec context for the video stream
codecCtx = pFormatCtx->streams[stream_index]->codec;

if(codecCtx->codec_type == CODEC_TYPE_AUDIO) {
// Set audio settings from codec info
wanted_spec.freq = codecCtx->sample_rate;
/* .... */
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;

if(SDL_OpenAudio (&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio : %s/n", SDL_GetError());
return -1;
}
}
codec = avcodec_find_decoder (codecCtx->codec_id);
if(!codec || (avcodec_open (codecCtx, codec) < 0)) {
fprintf(stderr, "Unsupported codec!/n");
return -1;
}

switch(codecCtx->codec_type) {
case CODEC_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio (0);
break;
case CODEC_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];

packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread (video_thread, is);
break;
default:
break;
}
}
This is pretty much the same as the code we had before, except now it's generalized for audio and video. Notice that instead of aCodecCtx, we've set up our big struct as the userdata for our audio callback. We've also saved the streams themselves as audio_st and video_st . We also have added our video queue and set it up in the same way we set up our audio queue. Most of the point is to launch the video and audio threads. These bits do it:
SDL_PauseAudio
(0);
break;

/* ...... */

is->video_tid = SDL_CreateThread (video_thread, is);
We remember SDL_PauseAudio () from last time, and SDL_CreateThread () is used as in the exact same way as before. We'll get back to our video_thread() function.

Before that, let's go back to the second half of our decode_thread() function. It's basically just a for loop that will read in a packet and put it on the right queue:

for(;;) {
if(is->quit) {
break;
}
// seek stuff goes here
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay (10);
continue;
}
if(av_read_frame (is->pFormatCtx, packet) < 0) {
if(url_ferror (&pFormatCtx->pb) == 0) {
SDL_Delay (100); /* no error; wait for user input */
continue;
} else {
break;
}
}
// Is this a packet from the video stream?
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet (packet);
}
}

这里没有什么新东西,除了我们给音频和视频队列限定了一个最大值并且我们添加一个检测读错误的函数。格式上下文里面有一个叫做pb的 ByteIOContext类型结构体。这个结构体是用来保存一些低级的文件信息。函数url_ferror用来检测结构体并发现是否有些读取文件错误。

在循环以后,我们的代码是用等待其余的程序结束和提示我们已经结束的。这些代码是有益的,因为它指示出了如何驱动事件--后面我们将显示影像。

while(!is->quit) {

SDL_Delay(100);

}

fail:

if(1){

SDL_Event event;

event.type = FF_QUIT_EVENT;

event.user.data1 = is;

SDL_PushEvent(&event);

}

return 0;

我们使用SDL常量SDL_USEREVENT来从用户事件中得到值。第一个用户事件的值应当是SDL_USEREVENT,下一个是 SDL_USEREVENT+1并且依此类推。在我们的程序中FF_QUIT_EVENT被定义成SDL_USEREVENT+2。如果喜欢,我们也可以 传递用户数据,在这里我们传递的是大结构体的指针。最后我们调用SDL_PushEvent()函数。在我们的事件分支中,我们只是像以前放入 SDL_QUIT_EVENT部分一样。我们将在自己的事件队列中详细讨论,现在只是确保我们正确放入了FF_QUIT_EVENT事件,我们将在后面捕 捉到它并且设置我们的退出标志quit。

得到帧:video_thread

当我们准备好解码器后,我们开始视频线程。这个线程从视频队列中读取包,把它解码成视频帧,然后调用queue_picture函数把处理好的帧放 入到图片队列中:

int video_thread(void *arg) {

VideoState *is = (VideoState *)arg;

AVPacket pkt1, *packet = &pkt1;

int len1, frameFinished;

AVFrame *pFrame;

pFrame = avcodec_alloc_frame();

for(;;) {

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

// means we quit getting packets

break;

}

// Decode video frame

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

packet->data, packet->size);

 

// Did we get a video frame?

if(frameFinished) {

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

break;

}

}

av_free_packet(packet);

}

av_free(pFrame);

return 0;

}

在这里的很多函数应该很熟悉吧。我们把avcodec_decode_video函数移到了这里,替换了一些参数,例如:我们把AVStream保 存在我 们自己的大结构体中,所以我们可以从那里得到编解码器的信息。我们仅仅是不断的从视频队列中取包一直到有人告诉我们要停止或者出错为止。

把帧队列化

让我们看一下保存解码后的帧pFrame到图像队列中去的函数。因为我们的图像队列是SDL的覆盖的集合(基本上不用让视频显示函数再做计算了), 我们需要把帧转换成相应的格式。我们保存到图像队列中的数据是我们自己做的一个结构体。

typedef struct VideoPicture {

SDL_Overlay *bmp;

int width, height;

int allocated;

} VideoPicture;

我们的大结构体有一个可以保存这些缓冲区。然而,我们需要自己来申请SDL_Overlay(注意:allocated标志会指明我们是否已经做了 这个申请的动作与否)。

为了使用这个队列,我们有两个指针--写入指针和读取指针。我们也要保证一定数量的实际数据在缓冲中。要写入到队列中,我们先要等待缓冲清空以便于 有位置 来保存我们的VideoPicture。然后我们检查看我们是否已经申请到了一个可以写入覆盖的索引号。如果没有,我们要申请一段空间。我们也要重新申请 缓冲如果窗口的大小已经改变。然而,为了避免被锁定,尽是避免在这里申请(我现在还不太清楚原因;我相信是为了避免在其它线程中调用SDL覆盖函数的原 因)。

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

 

VideoPicture *vp;

int dst_pix_fmt;

AVPicture pict;

 

 

SDL_LockMutex(is->pictq_mutex);

while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&

!is->quit) {

SDL_CondWait(is->pictq_cond, is->pictq_mutex);

}

SDL_UnlockMutex(is->pictq_mutex);

 

if(is->quit)

return -1;

 

// windex is set to 0 initially

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

 

 

if(!vp->bmp ||

vp->width != is->video_st->codec->width ||

vp->height != is->video_st->codec->height) {

SDL_Event event;

 

vp->allocated = 0;

 

event.type = FF_ALLOC_EVENT;

event.user.data1 = is;

SDL_PushEvent(&event);

 

 

SDL_LockMutex(is->pictq_mutex);

while(!vp->allocated && !is->quit) {

SDL_CondWait(is->pictq_cond, is->pictq_mutex);

}

SDL_UnlockMutex(is->pictq_mutex);

if(is->quit) {

return -1;

}

}

这里的事件机制与前面我们想要退出的时候看到的一样。我们已经定义了事件FF_ALLOC_EVENT作为SDL_USEREVENT。我们把事件 发到事件队列中然后等待申请内存的函数设置好条件变量。

让我们来看一看如何来修改事件循环:

for(;;) {

SDL_WaitEvent(&event);

switch(event.type) {

 

case FF_ALLOC_EVENT:

alloc_picture(event.user.data1);

break;

记住event.user.data1是我们的大结构体。就这么简单。让我们看一下alloc_picture()函数:

void alloc_picture(void *userdata) {

 

VideoState *is = (VideoState *)userdata;

VideoPicture *vp;

 

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

if(vp->bmp) {

// we already have one make another, bigger/smaller

SDL_FreeYUVOverlay(vp->bmp);

}

// Allocate a place to put our YUV image on that screen

vp->bmp = SDL_CreateYUVOverlay(is->video_st->codec->width,

is->video_st->codec->height,

SDL_YV12_OVERLAY,

screen);

vp->width = is->video_st->codec->width;

vp->height = is->video_st->codec->height;

 

SDL_LockMutex(is->pictq_mutex);

vp->allocated = 1;

SDL_CondSignal(is->pictq_cond);

SDL_UnlockMutex(is->pictq_mutex);

}

你可以看到我们把SDL_CreateYUVOverlay函数从主循环中移到了这里。这段代码应该完全可以自我注释。记住我们把高度和宽度保存到 VideoPicture结构体中因为我们需要保存我们的视频的大小没有因为某些原因而改变。

好,我们几乎已经全部解决并且可以申请到YUV覆盖和准备好接收图像。让我们回顾一下queue_picture并看一个拷贝帧到覆盖的代码。你应 该能认出其中的一部分:

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

if(vp->bmp) {

SDL_LockYUVOverlay(vp->bmp);

 

dst_pix_fmt = PIX_FMT_YUV420P;

 

pict.data[0] = vp->bmp->pixels[0];

pict.data[1] = vp->bmp->pixels[2];

pict.data[2] = vp->bmp->pixels[1];

 

pict.linesize[0] = vp->bmp->pitches[0];

pict.linesize[1] = vp->bmp->pitches[2];

pict.linesize[2] = vp->bmp->pitches[1];

 

// Convert the image into YUV format that SDL uses

img_convert(&pict, dst_pix_fmt,

(AVPicture *)pFrame, is->video_st->codec->pix_fmt,

is->video_st->codec->width, is->video_st->codec->height);

 

SDL_UnlockYUVOverlay(vp->bmp);

 

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

is->pictq_windex = 0;

}

SDL_LockMutex(is->pictq_mutex);

is->pictq_size++;

SDL_UnlockMutex(is->pictq_mutex);

}

return 0;

}

这部分代码和前面用到的一样,主要是简单的用我们的帧来填充YUV覆盖。最后一点只是简单的给队列加1。这个队列在写的时候会一直写入到满为止,在 读的时 候会一直读空为止。因此所有的都依赖于is->pictq_size值,这要求我们必需要锁定它。这里我们做的是增加写指针(在必要的时候采用轮转 的方式),然后锁定队列并且增加尺寸。现在我们的读者函数将会知道队列中有了更多的信息,当队列满的时候,我们的写入函数也会知道。

显示视频

这就是我们的视频线程。现在我们看过了几乎所有的线程除了一个--记得我们调用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)这种方式是很笨的。我们将在后面来讨论它。

基于Ultralytic实现多路摄像头目标检测 基于Ultralytic实现多路摄像头目标检测前面的文章分享了单路摄像头画面的YOLO目标检测,这篇文章主要分享一下多路摄像头画面的实时YOLO目标检测。测试环境在Windows系统下进行。 阅读详情

相关推荐

人工势场法三大致命缺陷?我用ROS+Gazebo做了组对比实验

本文通过ROS与Gazebo仿真实验,深入剖析了人工势场法(APF)在机器人路径规划中的三大经典问题:局部最小值、目标不可达和路径震荡。文章不仅复现了这些缺陷,还对比测试了虚拟目标点法、改进斥力场及运动平滑等主流改进方案,为工程选型提供了基于数据的实用建议。

dapp9builder的博客 215

ffmpeg threads_FFmpeg指定x265编码器线程数

FFmpeg的-threads参数FFmpeg调用编码器时,一般使用-threads参数对编码器使用的线程数进行设置。比如:ffmpeg -s 1920x1080 -framerate 25 -i input.yuv -c:v libx264 -threads 4 -y output.h264对于x264编码器,这个参数的意义是明确的,既是编码器占用的线程数,也是编码器并行处理的帧数。但对于x26...

weixin_42183453的博客 3558

Verilog中用于时序验证的系统任务[setup][hold][skew][width][recovery][removal]【STA静态时序分析】

下列时序检查语句错误的是() A. $setup(posedge clk, data, tSU) B. $hold(posedge clk, data, tHLD) C. $setuphold(posedge clk, data, tSU, tHLD) 答案:A 解析: 在时序检查函数中,$setup 函数比较特殊,格式是: $setup(data_event, reference_event, limit); 其他常见的检查是: $......(reference_...

DengFengLai123的博客 1万+

ffmpeg threads_ffmpeg多线程转码

最近需要用ffmpeg进行视频转码,考虑到ffmpeg这个工具本身支持多线程,而服务器也是多核的,想最大程度发挥服务器的计算能力。显然不能用单线程了,但是线程并不是越多越好,需要找到个平衡点。这就需要用time命令进行计时,并将该屏幕的输出重定向到文件,以便后期进行分析。假设我这里有个in.mp4的视频文件,分辨率为640*480,码率为580kbps,需要降低分辨率为320*240,降低码率为2...

weixin_35676504的博客 4019

别再只做游戏了!用虚幻引擎5(UE5)给你的工厂车间做个数字双胞胎(附数据对接教程)

本文详细介绍了如何利用虚幻引擎5(UE5)构建工业数字孪生系统,实现工厂车间的智能可视化。通过UE5的高质量渲染和实时数据处理能力,结合OPC UA和MQTT协议,打造高效的数据对接方案,提升异常识别和设备管理效率。文章还分享了模型优化、可视化设计及典型工业场景的实战案例,助力企业迈向工业4.0。

weixin_33738555的博客 206

完整IC设计流程解析&Makefile学习

本文介绍了decorder38项目的文件结构、自动化综合流程和Makefile使用方法。项目包含RTL到GDSII全流程文件,重点关注syn目录中的综合脚本。通过make dc命令运行综合流程,生成网表、约束和时序文件。关键报告如qor.rpt、area.rpt和min_vio.rpt用于分析综合质量、面积和时序。Makefile部分解释了其基本语法、IC设计中的典型结构,包括目标定义、命令执行和特殊声明,强调了其在自动化构建和依赖管理中的重要性。

m0_63352623的博客 855

Python多任务编程之线程

多任务编程: 作用:充分利用计算机多核资源,提高程序的运行效率。 实现方案:多进程;多线程 相关概念: 并发:多个任务在同一时间间隔内发生。表面上看像是多个任务同时进行,实际是任务在时间片上的轮转(即多个任务在内核上以极短的时间快速切换),也就是说,每个时刻只有一个任务占有内核资源。 并行:多个任务利用计算机多核资源而同时执行,称这些多个任务间为并行关系 一、线程概述 1、线程的概念 线程(Thread)是操作系统能够进行运算调度(分配内核)的最小单位,它被包含在进程之中,是进程的实际运作单位,

lszloz的博客 660

疾风气象大模型4.0白皮书

《疾风气象大模型白皮书摘要》 疾风气象大模型(J-AFM)是国内首个基于3D建模的气象智能平台,突破传统二维气象分析的局限,采用体素化技术构建"x-y-z-t"四维统一模型,实现从分钟级短临到30天跨时效的连续预测。核心技术包括:1)多源数据三维同化,融合雷达、卫星、风廓线等异构数据;2)物理约束AI解码器,嵌入能量守恒、湍流封闭等气象原理;3)行业适配插件,服务风电、光伏、航空等领域,如山地风场尾流校正、云影短临预测。平台支持批流一体推理与边缘部署,提供概率化输出和误差溯源能力,显著降

专注AI大模型,软件混淆,授权 538

FFmpeg学习笔记

FFmpeg学习笔记 1、官网下载 官网下载地址:http://ffmpeg.org/download.html 3大组件: 1、ffmpeg 转换多媒体内容; 2、ffplay 简介播放器。比如:ffplay 视频名称.mp4; 3、ffprobe 媒体参数分析工具。比如:ffprobe 视频名称.mp4; 2、视频格式转换 2.1、常见视频格式 视频格式 说明 RMVB 较为封闭 MKV 最开放 几乎支持所有的音视频 但体积太大 AVI 有点老 已经不适应最近的音视频 MOV

1853 2899

【1.2】Costas环的基本原理概述

Costas环是一种用于载波恢复的相干解调技术,可直接从BPSK等抑制载波信号中恢复本地载波并完成解调。其核心结构包含同相/正交双支路处理,通过混频、低通滤波后,利用I/Q支路信号乘积进行相位误差检测,经环路滤波器调节VCO输出频率,实现载波同步。课程将重点讲解Costas环的MATLAB仿真实现、系统性能分析及FPGA开发,包括信号产生、频偏模拟、滤波器设计等模块,并通过AWGN信道和不同频偏条件下的测试验证系统性能。该技术无需单独提取载波,在数字通信系统中具有重要应用价值。

fpga/matlab/simulink算法仿真工程 450

进阶06:C#连接TwinCAT 3使用ADS通讯

本文介绍了使用C#通过ADS协议与倍福TwinCAT3 PLC通讯的实现方法。主要内容包括:1)在两台电脑上安装TwinCAT3开发环境并建立网络连接;2)在TwinCAT中定义变量和结构体;3)使用C# Winform开发界面,通过TwinCAT.Ads.dll实现读写功能;4)实现主动读取和通知两种数据获取方式。详细说明了连接建立、变量定义、读写操作及事件通知的实现代码。该方案实现了对PLC寄存器数据的实时监控和操作,为工业控制系统的开发提供了参考。

weixin_38637405的博客 3688

【转】ffmpeg tutorial 4:创建线程 第二部分

 我们几乎做完了;我们仅仅剩了最后一件事:显示视频!下面就是video_display函数:void video_display(VideoState *is) {  SDL_Rect rect; VideoPicture *vp; AVPicture pict; float aspect_ratio; int w, h, x, y; i

code09的专栏 824

物联网与人工智能项目实战:构建智能农业监控系统

智能农业监控系统通过物联网技术实时监测农业环境中的各项数据(如土壤湿度、温度、光照强度等),并利用人工智能技术分析和预测农业生产的趋势,进而为农民提供决策支持。系统的核心目标是提高农作物的产量和品质,同时减少资源浪费,优化农田管理。环境监测:实时采集农田的温湿度、土壤湿度、光照等环境数据。作物生长监测:通过传感器和图像识别技术监测作物的生长状况。智能灌溉系统:根据土壤湿度和气候条件自动调整灌溉量。病虫害检测与预警:利用人工智能和图像识别技术自动检测作物病虫害,并及时给出预警。数据分析与决策支持。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 709

ffmpeg tutorial 创建线程

在手册中,这部分有的没有翻译。 流程大致是这样的: main-->定时器产生FF_REFRESH_EVENT事件-->产生decode_thread线程-->进入SDL事件循环   decode_thread-->初始化操作(很简单的找到解码库等)-->read包-->将包放入相应的队列中-->如果是视频流调用video_thread -->如果是音频流则设置audio_callback

hustbin的专栏 795

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

FFmpeg In Android - tutorial-4-Spawning Threads创建线程

Overview 概述 Last time we added audio support by taking advantage of SDL’s audio functions. SDL started a thread that made callbacks to a function we defined every time it needed audio. Now we’re going...

kingdam578的专栏 345

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

ffmpeg tutorial 4:创建线程

Spawning Threads Overview Last time we added audio support by taking advantage of SDL's audio functions. SDL started a thread that made callbacks to a function we defined every time it needed audio

w_ww_w的专栏 508

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

ffmpeg学习(2)--An ffmpeg and SDL Tutorial

ffmpeg学习(2)--An ffmpeg and SDL Tutorial 安装好ffmpeg后,就开始学习如何应用了,主要也就是熟悉一些常用API。 同事推荐了一个ffmpeg的自学教程--An ffmpeg and SDL Tutorial,觉得不错,就开始照敲代码了。这个教程的网址为:http://dranger.com/ffmpeg/tutorial01.html 学习过程中

gavin的专栏 5013

FFmpeg入门(3)-An ffmpeg and SDL Tutorial

ffmpeg学习(2)--An ffmpeg and SDL Tutorial 安装好ffmpeg后,就开始学习如何应用了,主要也就是熟悉一些常用API。 同事推荐了一个ffmpeg的自学教程--An ffmpeg and SDL Tutorial,觉得不错,就开始照敲代码了。这个教程的网址为:http://dranger.com/ffmpeg/tutorial01.html 学习过

s170262941的专栏 1565

音视频入门(学习ffmpeg tutorial之 iOS 视频多线程)

FFmpeg视频多线程 接上一章节使用多线程来播放音频,这里讲讲如何使用多线程来播放视频,首先需要明确的是,多线程播放视频比多线程播放音频要复杂,因为音频的播放可以直接使用audio_callback方法来控制音频硬件读取的缓存区大小,没有数据了就直接调用这个方法读一批数据进来,但是视频硬件读取数据是需要代码来控制视频缓冲区的读取的,因此需要再弄一个生产者消费者模式代码来展示图片帧。具体流程图如下...

无聊的专栏 461

An ffmpeg and SDL Tutorial 学习笔记(四)

官网: http://dranger.com/ffmpeg/tutorial04.html Tutorial 04: Spawning Threads Code: tutorial04.c Overview Last time we added audio support by taking advantage of SDL's audio functions.  SDL st

STN_LCD的专栏 607

【ffmpeg学习】Tutorial 04: Spawning Threads 将解析线程与视频解码线程分开

环境 FFmpeg:2013年9月9日 github master版本 SDL:SDL2 系统:macos 10.8 64位 ffmpeg编译参数:./configure --cc=clang --disable-everything --enable-libfdk_aac --enable-libmp3lame --enable-protocol=file --enable-proto

ashqal的专栏 4659

ffmpeg tutorial 4 初印象

目标    大纲式列出变化(v3 vs v4)

titer1的专栏 1004

ffmpeg Tutorial学习

转自:http://blog.youkuaiyun.com/gavinr/article/details/6973704 安装好ffmpeg后,就开始学习如何应用了,主要也就是熟悉一些常用API。 同事推荐了一个ffmpeg的自学教程--An ffmpeg and SDL Tutorial,觉得不错,就开始照敲代码了。这个教程的网址为:http://dranger.com/ffmpeg/tutoria

hustbin的专栏 660

FFmpeg的tutorial 学习

一、前言: 这是一个学习 FFmpeg 的 tutorial 系列。 这个是一个对初学者比较友好的FFmpeg学习教程,作者一步步引导我们实现了一个音视频同步的播放器。 参考链接: 原文地址: http://dranger.com/ffmpeg/ 比较新的代码Github地址: https://github.com/mpenkov/ffmpeg-tutorial...

weixin_30919235的博客 113

ffmpeg的tutorial中文版学习笔记(四)

源代码:tutorial04-1.c 综述 前面我们利用SDL的音频函数实现了对音频解码和播放的支持,我们定义一个包含音频回调函数callbacks的线程函数,当我们需要音频的时候就使SDL启动这个线程。现在我们将要对视频播放做同样的事情,这样能使代码更容易模块化和协作,尤其有利于音视频同步,那么我们从哪里开始呢? 首先注意到,我们的主函数需要做太多的事情:运行event循环,读pa

郑亚帅 430

【转】ffmpeg tutorial 5:同步视频

 指导5:同步视频如何同步视频 前面整个的一段时间,我们有了一个几乎无用的电影播放器。当然,它能播放视频,也能播放音频,但是它还不能被称为一部电影。那么我们还要做什么呢? PTS和DTS 幸运的是,音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面。音频流有采样,视频流有每秒的帧率。然而,如果我们只是简单的通过数帧 和乘以

code09的专栏 841
上一篇: 【转】ffmpeg tutorial 3:播放声音
下一篇: 【转】ffmpeg tutorial 4:创建线程 第二部分
code09
博客等级 码龄18年 31粉丝 56原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值