【再深入FFMPEG】libavdevice的作用

【再深入FFMPEG】过滤器Filter执行流程

再深入FFMPEG

libavdevice

是什么

libavdevice 是 FFmpeg 中的一个库,它提供了对各种音视频设备 的输入和输出支持。这包括摄像头、麦克风、音频接口、视频接口等。通过 libavdevice,你可以使用 FFmpeg 来处理来自各种设备的音视频数据。

具体来说,libavdevice 提供了一些设备的输入和输出协议,以便于 FFmpeg 可以直接与这些设备进行交互。比如,它包括了一些常见的设备输入协议,如 v4l2(Video for Linux 2,Linux 下的视频设备接口)、alsa(Advanced Linux Sound Architecture,Linux 下的音频设备接口)等。对于输出,它也支持将音视频数据输出到一些特定的设备,比如显示器、音频输出设备等。

比如可以使用 libavdevice 提供的设备输入协议 "android_camera" 来打开安卓设备的摄像头,并获取摄像头捕获的视频帧。

总的来说,libavdevice 提供了一个统一的接口,使得 FFmpeg 可以方便地与各种音视频设备进行交互,这使得它成为一个强大的多媒体处理工具。

获取数据

先通过avformat_open_input获取数据输入,这样做:

cpp 复制代码
AVInputFormat *input_format = av_find_input_format("android_camera"); 
// 打开摄像头设备
avformat_open_input(&format_context, device_name, input_format, NULL)

既然打开一个数据输入如此简单,那么avformat_open_input就必须要了解这个函数内部做了什么?

avformat_open_input

如果AVInputFormat参数为NULL的话,这里会根据filename的内容生成不同的AVInputFormat,比如:如果我们传递的filename是一个本地文件路径,那么这里就会生成一个读取本地文件的AVInputFormat,用来将数据内容封装成AVPacket。

如果不为NULL的话,也就是ffmpeg不会自动的去生成AVInputFormat,则会根据我们指定的AVInputFormat解析规则去解析数据。

如果传入的AVInputFormat的flags有AVFMT_NOFILR标记,则filename参数可以为NULL,否则会报错。

android_camera

android_camera是一个AVInputFormat,用来将安卓摄像头中的数据转成AVPACKET

当我们通过av_read_frame读取数据时,就会调用到AVInputFormat的read_paket函数

通过read_paket函数获取到一个AVPACKET,功能由具体的AVInputFormat实现,这里以android_camera举例:

android_camera会从他的avpacket队列中获取一个值,这里的av_thread_message_queue_recv会根据flags执行等待或者不等待,而android-camera会执行等待,直到队列中由新值加入进来。

对于flags只有两种模式:AV_THREAD_MESSAGE_NONBLOCK(非阻塞模式)和0(默认模式 阻塞模式)

在 FFmpeg 中,av_thread_message_queue_recv 函数用于从线程消息队列中接收消息。这个函数的作用是从指定的线程消息队列中获取消息,并将消息的内容复制到用户提供的缓冲区中。

通常情况下,线程之间需要进行通信,而线程消息队列是一种常见的线程间通信的方式。通过将消息放入队列,一个线程可以向另一个线程发送数据或指令,而接收线程则可以使用 av_thread_message_queue_recv 函数来接收并处理这些消息。

以下是 av_thread_message_queue_recv 函数的基本用法:

cpp 复制代码
int av_thread_message_queue_recv(AVThreadMessageQueue *mq, AVThreadMessage *msg, int flags);
  • mq 是指向线程消息队列的指针,用于指定从哪个消息队列中接收消息。
  • msg 是一个指向 AVThreadMessage 结构的指针,用于存储接收到的消息内容。
  • flags 是一些控制接收行为的标志位。

在调用 av_thread_message_queue_recv 函数后,如果消息队列中有消息可用,它将把消息的内容复制到 msg 指向的结构中,并返回一个非负值表示成功接收消息。如果消息队列为空,它可能会根据标志位的设置来等待一段时间,或者立即返回一个指示队列为空的值。

android_camera加入数据到avpacket队列

对于从摄像头获取数据,需要设置监听器,当摄像头有新数据时就会回调该监听器

而这个监听器是由AImageReader_setImageListener设置的

cpp 复制代码
media_status_t AImageReader_setImageListener(  
    AImageReader* reader, AImageReader_ImageListener* listener) __INTRODUCED_IN(24);

AImageReader

源码位置:frameworks/av/media/ndk/NdkImageReader.cpp

edit

首先需要先获取一个AImageReader

通过AImageReader_new可以获取到一个Reader,第3个参数是获取的图像数据格式:关于支持的格式在NdkImage.h中

但是他并不会对格式进行转换

这个地方有点没看懂,意思应该是默认只支持YUV格式(yuv格式包括YV12),这里的format应该是我们期望获取到的相机的原始数据格式是什么样的,如果不支持则返回错误信息。如果都属于YUV格式,则将AImageReader中的mHalFormat属性重新设置为获取到的图像的Formate格式(这个问题先保留)

前两个参数是图像的默认宽高,如果相机的原始图像数据是HAL_PIXEL_FORMAT_BLOB格式的话,就会使用该默认的宽高,如果不是这种数据就是使用图像的真实宽高。

在Android的相机API中,HAL_PIXEL_FORMAT_BLOB是一个特殊的像素格式,它被用来处理非图像数据或者特殊的图像数据。这种格式的数据通常是不透明的,即应用程序通常不能直接访问或修改这种格式的数据。

在实际使用中,HAL_PIXEL_FORMAT_BLOB常常被用来处理JPEG压缩的图像数据。当相机设置为这种格式时,相机会直接输出JPEG格式的图像数据,而不是常见的YUV或者RAW格式的图像数据。这样做的好处是,JPEG格式的图像数据体积较小,可以节省存储空间。而且,因为JPEG格式广泛被支持,所以这种格式的图像数据可以直接被大多数的图像查看或处理软件使用。

需要注意的是,HAL_PIXEL_FORMAT_BLOB格式的数据通常不能直接用来进行图像处理。如果你需要对图像进行处理,你可能需要将这种格式的数据转换为其他格式,例如YUV或者RGB格式。

AImageReader_ImageListener

js 复制代码
typedef void (*AImageReader_ImageCallback)(void* context, AImageReader* reader);

通过reader获取Image

js 复制代码
media_status_t AImageReader_acquireLatestImage(AImageReader* reader, /*out*/AImage** image)

Image转Packet

需要通过以下方法设置packet的一些属性:

  1. pts:通过AImage_getTimestamp获取到时间戳
  2. data: av_image_copy_to_buffer设置图像数据, 其中的参数需要通过:av_image_get_buffer_size、AImage_getPlaneRowStride和AImage_getPlaneData获取

一些函数

AImage_getPlanePixelStride

AImage_getPlanePixelStride 是一个Android NDK(Native Development Kit)中的函数,用于处理Android平台上的图像。该函数的作用是获取给定图像平面的像素跨距(Pixel Stride)。像素跨距表示在图像的连续像素之间的字节间距。

在处理多平面图像(例如YUV格式图像)时,了解像素跨距是很重要的。多平面图像通常将图像的不同分量(如亮度和色度分量)分开存储在不同的平面中。这些平面可能具有不同的像素跨距,即在连续像素之间的字节间距可能不同。

AImage_getPlanePixelStride 函数接收两个参数:

  1. const AImage* image - 指向要查询的AImage对象的指针。
  2. int32_t planeIdx - 要查询的图像平面的索引。

平面索引(plane index)通常是指YUV这三个平面。在多平面图像格式(如YUV)中,图像的不同分量(亮度和色度分量)被分开存储在不同的平面中。在YUV图像中,有三个平面:

  1. Y平面(亮度平面):包含图像的亮度信息,每个像素都有一个亮度值。在YUV图像中,Y平面的索引通常为0。
  2. U平面(色度平面):包含图像的蓝色色差信息,通常以子采样的方式存储。在YUV图像中,U平面的索引通常为1。
  3. V平面(色度平面):包含图像的红色色差信息,通常以子采样的方式存储。在YUV图像中,V平面的索引通常为2。

由于NV12和NV12的UV平面是合并存储的,所以一般会占两个字节,而一般的YUV420P,分别存储各占一个字节。但是他们的平面数还是3个

AImage_getPlaneData

AImage_getPlaneData函数是Android平台上用于获取图像数据的函数。这个函数可以返回一个指向图像数据的指针,以及这些数据的字节大小。

在YUV格式的图像中,AImage_getPlaneData可以用来获取Y、U、V平面的数据。例如,你可以使用AImage_getPlaneData函数获取Y平面的数据,然后再使用AImage_getPlanePixelStrideAImage_getPlaneRowStride函数来确定如何遍历这些数据。

这个函数的原型如下:

cpp 复制代码
media_status_t AImage_getPlaneData(
    const AImage* image,
    int32_t planeIdx,
    uint8_t** data,
    int* dataLength);

其中,image是要处理的图像,planeIdx是要获取数据的平面索引(对于YUV 420格式的图像,0表示Y平面,1表示U平面,2表示V平面),data是一个指向指针的指针,该函数会设置这个指针指向平面的数据,dataLength是一个指向整数的指针,该函数会设置这个整数为数据的字节大小。

需要注意的是,这个函数并不会复制数据,返回的指针直接指向图像的内存,因此你需要确保在调用AImage_delete函数删除图像之前不要使用这个指针。

在NV12格式中,Y平面的数据排列在前,然后是V平面,最后是U平面。所以,如果您发现U平面的*data大于V平面的*data,那么这很可能意味着图像是YV12格式的。

在NV12格式中,Y平面的数据排列在前,然后是U和V平面的交错排列。也就是说,U和V平面的数据是交替存储的。在这种情况下,U平面的*data会小于V平面的*data

特别

  1. HAL_PIXEL_FORMAT_YCrCb_420_SP在Android的图像格式中,对应安卓FFMPEG下的AV_PIX_FMT_NV12或者AV_PIX_FMT_NV21,具体区分需要通过AImage_getPlaneData,上面有写。
相关推荐
工业3D_大熊19 分钟前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
暮色_年华33 分钟前
Modern Effective C++ Item 11:优先考虑使用deleted函数而非使用未定义的私有声明
c++
流星白龙35 分钟前
【C++习题】10.反转字符串中的单词 lll
开发语言·c++
Smile丶凉轩1 小时前
微服务即时通讯系统的实现(服务端)----(1)
c++·git·微服务·github
大白要努力!1 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
萝卜兽编程1 小时前
优先级队列
c++·算法
天空中的野鸟2 小时前
Android音频采集
android·音视频
珹洺3 小时前
C语言数据结构——详细讲解 双链表
c语言·开发语言·网络·数据结构·c++·算法·leetcode
孙同学要努力3 小时前
C++知识整理day1——前置基础知识整理(命名空间、输入输出、函数重载、引用)
开发语言·c++
沐泽Mu3 小时前
嵌入式学习-C嘎嘎-Day05
开发语言·c++·学习