Pyav代码分析

news/2025/3/14 19:20:26/

PyAV提供了ffmpeg的python接口,但实际是它只是使用ffmpeg做后端,使用Cython封装了ffmpeg的接口,所以实际调用的还是ffmpeg。

也就是说,PyAV用类封装了ffmpeg提供的API,如果想要使用,关键还是要看懂其整体架构。
PYAV用类封装了ffmpeg的几个关键结构体

名称作用
packet封装了ffmpegde AVPacket
frame封装了ffmpeg的AVframe
stream封装了ffmpeg的AVStream
option封装了ffmpeg的AVOption
InputContainer封装了ffmpeg的avformat_open_input demux
OutputContainer封装了ffmpeg的av_interleaved_write_frame mux
CodecContext封装了ffmpeg codec相关代码

具体使用的,如果你有自己的ffmpeg,那么先编译安装自己的ffmpeg,然后:

pip install av --no-binary av

如果没有自己的ffmpeg::

pip install av

安装好之后就可以使用了。

下面先看几个简单的案例:

import os
import subprocess
import logging
import timelogging.basicConfig(level=logging.DEBUG)
logging.getLogger('libav').setLevel(logging.DEBUG)import av
import av.datasets# We want an H.264 stream in the Annex B byte-stream format.
# We haven't exposed bitstream filters yet, so we're gonna use the `ffmpeg` CLI.
h264_path = "libx264_640x360_baseline_5_frames.h264"
# if not os.path.exists(h264_path):
#     subprocess.check_call(
#         [
#             "ffmpeg",
#             "-i",
#             av.datasets.curated("zzsin_1920x1080_60fps_60s.mp4"),
#             "-vcodec",
#             "copy",
#             "-an",
#             "-bsf:v",
#             "h264_mp4toannexb",
#             h264_path,
#         ]
#     )fh = open(h264_path, "rb")codec = av.CodecContext.create("h264_efcodec", "r")
codec.options={"hw_id":"15"}print(codec.name)
first= True
count=0
while True:chunk = fh.read(1 << 16)packets = codec.parse(chunk)print("Parsed {} packets from {} bytes:".format(len(packets), len(chunk)))for packet in packets:print("   ", packet)frames = codec.decode(packet)if first:time.sleep(2)first=Falsefor frame in frames:print("       ", frame)count+=1print('--count:%d--'%count)frame.to_image().save("night-sky.{:04d}.jpg".format(count),quality=80,)# We wait until the end to bail so that the last empty `buf` flushes# the parser.if not chunk:breakp=av.Packet(None)
print("send eos:", p)
frames = codec.decode(p) 
for frame in frames:print("       ", frame)count+=1print('--count:%d--'%count)frame.to_image().save("night-sky.{:04d}.jpg".format(count),quality=80,)print('all count:%d'%count)

上面是通过创建codec来进行解码的案例,可以在创建的时候指定解码器名称以及可以设置option。这里注意一点就是这里只能parse annexb格式的视频流 AVCC的视频流是不能在这里解析的。

下面是另外一个demux codec的案例:

import timeimport av
import av.datasetscontainer = av.open('ocr_400_400_5_frames.mp4')
first=Truecount=0
start_time = time.time()
for packet in container.demux():print(packet)for frame in packet.decode():print(frame)count+=1print('---frame:%d---'%count)if first:time.sleep(2)first=Falseauto_time = time.time() - start_time
container.close()
print('all frame:%d',count)

这里的codec是container中内置的一个解码器,这里的解码器是无法自主选择具体使用那个解码器的。

综合上面两个案例,我们可以使用下面的方法来解码:

import os
import subprocess
import logging
import timeimport avlogging.basicConfig(level=logging.DEBUG)
logging.getLogger('libav').setLevel(logging.DEBUG)h264_path = "ocr_400_400_5_frames.mp4"
input_ = av.open(h264_path,options={"vsync":"0"})
in_stream = input_.streams.video[0]codec = av.CodecContext.create("h264_efcodec", "r")
codec.options={"hw_id":"15"}
# codec.options={"device_id":"0"}
print(codec.name)
# print(codec.extradata_size)
codec.extradata =in_stream.codec_context.extradatafirst=True
num = 0for packet in input_.demux(in_stream):print('----packet---')packet.dts =0packet.pts = 0print("   ", packet)frames = codec.decode(packet)print('---after decode---')if first:time.sleep(2)first=Falsefor frame in frames:print("       ", frame)num+=1print('-----frame:%d-----'%num)print('all:%d'%num)

上面这个案例结合了第一个和第二个解码的使用方法,在这里我们采用demux+decode(自己设置的解码器)。不要觉得这里很简单,这是我看完整个封装源代码才搞清楚的,当然这里唯一的缺点是inpoutcontainer内部为了分配condec,多占用了一些内存,不过这也无所谓了。

看完上面实例,可能发现一个sleep(2),为什么要加这句?主要是因为,我们硬件解码器open()的时候花费的时间较长,这里增加sleep函数来等待底下硬件解码器完全启动,不然会出现所有的输入数据送完了,解码器一帧数据都还没有解码出来。这里又引出PyAV的一个局限,它只能调用封装后的decode()接口,无法调用更加细粒度的ffmpeg接口,导致无法像ffmpeg那样通过循环调用avcodec_receive_frame()来取解码后的数据。

static int decode(AVCodecContext *dec_ctx) {int ret;AVPacket packet;AVFrame *p_frame;int eos = 0;p_frame = av_frame_alloc();while(1) {ret = av_read_frame(g_ifmt_ctx, &packet);if (ret == AVERROR_EOF) {av_log(g_dec_ctx, AV_LOG_INFO, "av_read_frame got eof\n");eos = 1;} else if (ret < 0) {av_log(g_dec_ctx, AV_LOG_ERROR, "av_read_frame failed, ret(%d)\n", ret);goto fail;}if (packet.stream_index != video_stream_idx) {av_packet_unref(&packet);continue;}ret = avcodec_send_packet(dec_ctx, &packet);if (ret < 0) {av_log(dec_ctx, AV_LOG_ERROR,"send pkt failed, ret(%d), %s, %d\n", ret, __FILE__, __LINE__);goto fail;}
//这里就是最后循环取出解码器中的yuv数据while (ret >= 0 || eos) {ret = avcodec_receive_frame(dec_ctx, p_frame);if (ret == AVERROR_EOF) {av_log(g_dec_ctx, AV_LOG_INFO, "dec receive eos\n");av_frame_unref(p_frame);av_frame_free(&p_frame);return 0;} else if (ret == 0) {save_yuv_file(dec_ctx, p_frame);av_frame_unref(p_frame);} else if (ret < 0 && ret != AVERROR(EAGAIN)) {av_log(dec_ctx, AV_LOG_ERROR, "receive frame failed\n");goto fail;}}av_packet_unref(&packet);}fail:av_frame_free(&p_frame);return -1;
}

到这里为止,Pyav基础用法基本完成。接下来讲一下架构。
Packet类:
主要封装了AVPacket,提供了一些可以set/get packet成员的一些property,里面函数有to_bytes()可以将data数据转为bytes对象,另外还有一个decode(),是通过其内部stream的指针去指向codec,然后去解码。

序号Value
成员变量-1AVPacket* ptr
成员变量-2Stream _stream
Propertystream_index
Propertystream
Propertytime_base
Propertypts
Propertydts
Propertypos
Propertysize
Propertyis_keyframe
Propertyis_corrupt
Propertybuffer_size(也就是packet的datasize)
Propertybuffer_ptr
Propertyto_bytes(将data转为python的bytes)
Fundecode(self._stream.decode(self))
构造self.ptr = lib.av_packet_alloc()
析构lib.av_packet_free(&self.ptr)

Frame类,这是一个基类,所以里面只有基础信息

序号Value
成员变量-1AVFrame *ptr
成员变量-2int index
成员变量-3AVRational _time_base
成员变量-3_SideDataContainer _side_data
Propertydts
Propertypts
Propertytime(The presentation time in seconds for this frame)
Propertytime_base(fractions.Fraction)
Propertyis_corrupt( Is this frame corrupt?)
Propertyside_data
构造self.ptr = lib.av_frame_alloc()
析构lib.av_frame_free(&self.ptr)

VideoFrame类:
该类继承了Frame类,除了提供了获取avframe类中的变量外,还提供了几个函数,可以csc颜色空间转换,保存jpg,或者从jpg,nump数组中转为Frame.

序号Value
成员变量-1VideoReformatter reformatter
成员变量-2VideoFormat format
Propertywidth
Propertyheight
Propertykey_frame
Propertyinterlaced_frame
Propertypict_type
Propertyplanes
Funto_rgb()( return self.reformat(format=“rgb24”, **kwargs))
Funto_image(可以保存为jpg)
Funto_ndarray()
Funfrom_image(img)
Funfrom_ndarray()

Stream类:
在stream类中还包含了两个其它的类:ContainerCodecContext

序号Value
成员变量-1AVStream *ptr
成员变量-2Container container
成员变量-3CodecContext codec_context
成员变量-4dict metadata
Propertyid
Propertyprofile
Propertyindex
Propertyaverage_rate
Propertybase_rate
Propertyguessed_rate
Propertystart_time
Propertyduration
Propertyframes(The number of frames this stream contains.)
Propertylanguage
PropertyType( Examples: 'audio', 'video', 'subtitle'.)
Funencode()
Fundecode()
Funget()/det() att

http://www.ppmy.cn/news/822276.html

相关文章

山西电力市场日前价格预测【2023-07-12】

日前价格预测 预测明日&#xff08;2023-07-12&#xff09;山西电力市场全天平均日前电价为446.44元/MWh。其中&#xff0c;最高日前价格为584.92元/MWh&#xff0c;预计出现在12: 00。最低日前电价为325.62元/MWh&#xff0c;预计出现在00: 30。 价差方向预测 1&#xff1a;实…

RocketMQ5.0消息消费<二> _ 消息队列负载均衡机制

RocketMQ5.0消息消费&#xff1c;二&#xff1e; _ 消息队列负载均衡机制 一、消费队列负载均衡概览 RocketMQ默认一个主题下有4个消费队列&#xff0c;集群模式下同一消费组内要求每个消费队列在同一时刻只能被一个消费者消费。那么集群模式下多个消费者是如何负载主题的多个…

数据结构--哈夫曼树

数据结构–哈夫曼树 带权路径长度 结点的 权 \color{red}权 权:有某种现实含义的数值&#xff08;如:表示结点的重要性等) 结点的带权路径长度 \color{red}结点的带权路径长度 结点的带权路径长度:从树的根到该结点的路径长度(经过的边数&#xff09;与该结点上权值的乘积 树的…

amd一键超频怎么用_AMD新版显卡驱动为“肾上腺素 2019”:支持一键超频,语音截屏...

原标题&#xff1a;AMD新版显卡驱动为“肾上腺素 2019”&#xff1a;支持一键超频&#xff0c;语音截屏 熟悉AMD显卡的各位肯定记得&#xff0c;从2014年开始&#xff0c;AMD每年年末都会为用户带来一款年度版驱动更新&#xff0c;而这种年度版驱动在带来前所未有的新特性的同时…

三种SQL实现聚合字段合并(presto、hive、mysql)

需求&#xff1a;按照项目名&#xff0c;以逗号合并参与人 presto select item_name,array_join(array_agg(name),,) as group_name from test.test_04 group by item_name order by item_name hive select item_name,concat_ws(,,collect_set(name)) as group_name from tes…

MBP禁用AMD显卡

MBP禁用AMD显卡 MacBook Pro花屏终极解决方案 1、先解决 SIP 重启按住controlCommandR&#xff0c;进入恢复模式&#xff0c;打开Terminal。 csrutildisable 2、再重启电脑 用 CommandS 进入命令行终端模式&#xff0c;删除AMD驱动。 sudo -s 密码 rm -rf /System/Library/E…

Mac 远程控制(远程桌面)工具推荐

1. RayLink 所有功能都可以免费使用的远程控制工具&#xff0c;支持Mac、Windows、Android、iPhone、iPad等平台&#xff0c;功能多且强大&#xff0c;远程连接速度快&#xff0c;稳定&#xff0c;首选的工具! 官网&#xff1a;RayLink远程控制软件-免费真高清超流畅远程控制…

amd核芯显卡控制面板自定义分辨率_经常升级显卡驱动有必要吗?实测告诉你

近日&#xff0c;AMD更新了旗下显卡的Adrenalin 2020 Edition驱动&#xff0c;目前版本迭代到了20.5.1正式版&#xff0c;本期内容就想通过实测来跟大家分析一下第一时间升级厂商的显卡驱动是否真的有必要&#xff1f; 都说A卡驱动一版一鸡血&#xff0c;年更驱动迭代更是堪比一…