再深化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
获取数据输入,这样做:
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
函数的基本用法:
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
设置的
media_status_t AImageReader_setImageListener(
AImageReader* reader, AImageReader_ImageListener* listener) __INTRODUCED_IN(24);
AImageReader
源码位置:frameworks/av/media/ndk/NdkImageReader.cpp
首先需求先获取一个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
typedef void (*AImageReader_ImageCallback)(void* context, AImageReader* reader);
经过reader获取Image
media_status_t AImageReader_acquireLatestImage(AImageReader* reader, /*out*/AImage** image)
Image转Packet
需求经过以下方法设置packet的一些特点:
- pts:经过
AImage_getTimestamp
获取到时间戳 - 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
函数接纳两个参数:
-
const AImage* image
– 指向要查询的AImage目标的指针。 -
int32_t planeIdx
– 要查询的图画平面的索引。
平面索引(plane index)一般是指YUV这三个平面。在多平面图画格局(如YUV)中,图画的不同重量(亮度和色度重量)被分开存储在不同的平面中。在YUV图画中,有三个平面:
- Y平面(亮度平面):包含图画的亮度信息,每个像素都有一个亮度值。在YUV图画中,Y平面的索引一般为0。
- U平面(色度平面):包含图画的蓝色色差信息,一般以子采样的方法存储。在YUV图画中,U平面的索引一般为1。
- V平面(色度平面):包含图画的红色色差信息,一般以子采样的方法存储。在YUV图画中,V平面的索引一般为2。
由于NV12和NV12的UV平面是合并存储的,所以一般会占两个字节,而一般的YUV420P,别离存储各占一个字节。可是他们的平面数仍是3个
AImage_getPlaneData
AImage_getPlaneData
函数是Android平台上用于获取图画数据的函数。这个函数能够回来一个指向图画数据的指针,以及这些数据的字节巨细。
在YUV格局的图画中,AImage_getPlaneData
能够用来获取Y、U、V平面的数据。例如,你能够运用AImage_getPlaneData
函数获取Y平面的数据,然后再运用AImage_getPlanePixelStride
和AImage_getPlaneRowStride
函数来确定如何遍历这些数据。
这个函数的原型如下:
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
。
特别
-
HAL_PIXEL_FORMAT_YCrCb_420_SP
在Android的图画格局中,对应安卓FFMPEG下的AV_PIX_FMT_NV12或许AV_PIX_FMT_NV21,具体区分需求经过AImage_getPlaneData,上面有写。