使用FFmpeg+SDL打开Mac摄像头

来源:互联网 发布:c语言英文怎么说 编辑:程序博客网 时间:2024/05/30 04:30

最近由于项目涉及到音视频方面的一点东西,所以研究了一波FFmpeg,其实我对这方面是非常有兴趣的,但是由于自己不是计算机专业出身,很多其他的东西都不了解,所以只能叹息心有余而力不足。FFmpeg是一个开源的东西,几乎没有文档,幸运的是有雷神为我们铺下了一条学习的道路,我也是跟着这条道路在走下去。可惜的是2016年7月雷神就已离我们而去,我们虽然年纪相仿,但是差距实在是大,我也很膜拜这位年轻的技术大神。写本文的目的有两个,一是缅怀雷霄骅博士,感谢他为我们做出的巨大贡献,二是我在学习的时候使用的是Mac电脑,却没发现有一篇讲述打开Mac摄像头的代码,几乎都是使用的命令行打开,于是我根据雷神的代码,加以修改,邯郸学步,写了这个打开摄像头的程序,权当学习。本人也将继续学习下去。

  • 列出设备
  • 打开设备
  • 查询流信息解码器
  • 分配对应数据结构
  • 初始化SDL
  • 读取数据解码填充
  • 程序退出
  • 演示

列出设备

写这个程序的第一步,当然是要知道机器有什么设备,查询设备的代码如下:

调用该函数显示的结果如下,其中”avfoundation”是指定的表示Mac/iOS端的输入设备

可以看到,在我的机器上,有两个视频设备及一个音频设备,分别是

  • [0] FaceTime HD Camera (高清摄像头)
  • [1] Capture screen 0 (屏幕录制)
  • [0] Built-in Microphone (内建麦克风)

我们在编译安装FFmpeg之后,会拿到一个FFmpeg命令,上述代码,就相当于一条FFmpeg命令:

打开设备

打开设备和查看设备的代码几乎一样,如下图所示:

其实跑起来之后会发现,根本无法打开摄像头,查看终端,发现会出现一些提示,我们需要为Mac的摄像头设置一些参数,如下所示:

设置参数之后的代码如下:

区别就在于红色线框标出的地方,根据上面的提示我们知道,我们必须为摄像头设置framerate参数,那么pixel_format的设置自然也是一样的,当你设置了framerate,但没有设置pixel_fromat就会出现类似的提示,我们这里选择uyvy422格式。同时使用video_size 设置视频的宽高,否则默认宽高是320x240, 中间的x号,其实是个英文字符小写的x。调用此函数,我们看到摄像头旁边的绿灯亮了起来,说明摄像头已经被成功打开。

查询流信息、解码器

接下来要做的,就是查询流信息了和对应的解码器了,代码如下所示:


首先,调用avformat_find_stream_info查询流信息,其中nb_streams表示流的个数,如果codec_type 是 AVMEDIA_TYPE_VIDEO,说明我们已经找到视频流,保留索引。

然后,通过codec_id找到对应的解码器,分配AVCodecContext,并使用avcodec_parameters_to_context方法设置对应的参数。之后调用avcodec_open2打开解码器。

分配对应数据结构

首先,我们分配一个AVPacket,这个结构是用来接收未解码的视频数据,同时分配一个AVFrame用于接收解码后的数据,也许还注意到有一个frame_yuv,因为摄像头的数据是UYVY422的格式的,但是一般的视频格式是YUV420格式,所以我们使用一个frame_yuv来接收转换后的数据,同时初始化一个SwsContext用于将数据格式从AV_PIX_FMT_UYVY422转化成AV_PIX_FMT_YUV420P。并调用av_image_fill_arrays来填充缓冲区

初始化SDL

我们初始化SDL用于显示数据,并创建窗口、渲染器和纹理,代码如下:

其中,SDL也支持UYVY422格式的数据,我们可以将格式修改成SDL_PIXELFORMAT_UYVY,这样,我们不需要转换数据,便可以直接显示。之后调用SDL_CreateThread创建线程,线程的处理方法如下:

自定义了两个消息,一个SDL_EVENT_INTERFACE_FRESH用于刷新数据,一个SDL_EVENT_QUIT用于退出SDL。我们每隔40 ms发送一个刷新数据事件,然后在主线程中使用SDL_WaitEvent等待事件发生。然后读取数据帧,解码,填充即可。

读取数据,解码填充

当一个SDL_EVENT_INTERFACE_FRESH事件到来的时候,我们就可以去刷新数据,首先使用av_read_frame读取一帧数据,然后使用avcodec_send_packet将未解码的数据发送给解码器进行解码,再使用avcodec_receive_frame从解码器接收解码后的数据,并填充到SDL中,代码如下所示:


如果我们最终使用YUV420格式,则需要使用sws_scale将数据从UYVY422转换到YUV420格式,然后填充,否则,我们可以直接填充,

SDL_UpdateTexture(sdlTexture, &sdlRect, frame->data[0], frame->linesize[0]);

而不再使用SDL_UpdateYUVTexture。同时使用fwrite将YUV数据写入到文件。

程序退出

我们还定义了一个SDL_EVENT_QUIT消息,当收到这个消息的时候,我们就退出循环,最后的代码如下:

当键盘按下的时候,我们使用SDL_GetKeyboardState拿到按下的键,如果按下的是Q键,则设置thread_exit标志为1, 通知子线程退出。

演示

最终的结果如下所示:

生成的YUV文件,我们使用YUVPlayer查看,如下图所示:

本人的长相确实有点丑,这点要承认,所以就不露脸来吓唬看过本文的网友了。如果将avformat_open_input中的”0”替换成”1”,即可录制屏幕,如下图所示:

最后再次感谢雷神,如果没有他,我甚至很多人都不知道FFmpeg应该怎么入门。我也会继续学习下去。当然毕竟我不是这个专业的,学FFmpeg只是为了项目罢了,本人纯属初学,如有错漏,希望批评指正。最后附上雷神博客地址:
雷神博客地址

原创粉丝点击