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 time
logging. basicConfig( level= logging. DEBUG)
logging. getLogger( 'libav' ) . setLevel( logging. DEBUG)
import av
import av. datasets
h264_path = "libx264_640x360_baseline_5_frames.h264"
fh = open ( h264_path, "rb" )
codec = av. CodecContext. create( "h264_efcodec" , "r" )
codec. options= { "hw_id" : "15" }
codec. open ( )
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= False
for frame in frames:
print ( " " , frame)
count+= 1
print ( '--count:%d--' % count)
frame. to_image( ) . save( "night-sky.{:04d}.jpg" . format ( count) , quality= 80 , )
if not chunk:
break
p= av. Packet( None )
print ( "send eos:" , p)
frames = codec. decode( p)
for frame in frames:
print ( " " , frame)
count+= 1
print ( '--count:%d--' % count)
frame. to_image( ) . save( "night-sky.{:04d}.jpg" . format ( count) , quality= 80 , )
print ( 'all count:%d' % count)
codec. close( )
上面是通过创建codec来进行解码的案例,可以在创建的时候指定解码器名称以及可以设置option。这里注意一点就是这里只能parse annexb格式的视频流 AVCC的视频流是不能在这里解析的。
下面是另外一个demux codec的案例:
import time
import av
import av. datasets
container = av. open ( 'ocr_400_400_5_frames.mp4' )
first= True
count= 0
start_time = time. time( )
for packet in container. demux( ) :
print ( packet)
for frame in packet. decode( ) :
print ( frame)
count+= 1
print ( '---frame:%d---' % count)
if first:
time. sleep( 2 )
first= False
auto_time = time. time( ) - start_time
container. close( )
print ( 'all frame:%d' , count)
这里的codec是container中内置的一个解码器,这里的解码器是无法自主选择具体使用那个解码器的。
综合上面两个案例,我们可以使用下面的方法来解码:
import os
import subprocess
import logging
import time
import av
logging. 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. open ( )
print ( codec. name)
codec. extradata = in_stream. codec_context. extradata
first= True
num = 0
for packet in input_. demux( in_stream) :
print ( '----packet---' )
packet. dts = 0
packet. pts = 0
print ( " " , packet)
frames = codec. decode( packet)
print ( '---after decode---' )
if first:
time. sleep( 2 )
first= False
for frame in frames:
print ( " " , frame)
num+= 1
print ( '-----frame:%d-----' % num)
print ( 'all:%d' % num)
codec. close( )
上面这个案例结合了第一个和第二个解码的使用方法,在这里我们采用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;
}
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 成员变量-1 AVPacket* ptr 成员变量-2 Stream _stream Property stream_index Property stream Property time_base Property pts Property dts Property pos Property size Property is_keyframe Property is_corrupt Property buffer_size(也就是packet的datasize) Property buffer_ptr Property to_bytes(将data转为python的bytes) Fun decode(self._stream.decode(self)) 构造 self.ptr = lib.av_packet_alloc() 析构 lib.av_packet_free(&self.ptr)
Frame 类,这是一个基类,所以里面只有基础信息
序号 Value 成员变量-1 AVFrame *ptr 成员变量-2 int index 成员变量-3 AVRational _time_base 成员变量-3 _SideDataContainer _side_data Property dts Property pts Property time(The presentation time in seconds for this frame) Property time_base(fractions.Fraction) Property is_corrupt( Is this frame corrupt?) Property side_data 构造 self.ptr = lib.av_frame_alloc() 析构 lib.av_frame_free(&self.ptr)
VideoFrame 类: 该类继承了Frame类,除了提供了获取avframe类中的变量外,还提供了几个函数,可以csc颜色空间转换,保存jpg,或者从jpg,nump数组中转为Frame.
序号 Value 成员变量-1 VideoReformatter reformatter 成员变量-2 VideoFormat format Property width Property height Property key_frame Property interlaced_frame Property pict_type Property planes Fun to_rgb()( return self.reformat(format=“rgb24”, **kwargs)) Fun to_image(可以保存为jpg) Fun to_ndarray() Fun from_image(img) Fun from_ndarray()
Stream 类: 在stream类中还包含了两个其它的类:Container
和CodecContext
序号 Value 成员变量-1 AVStream *ptr 成员变量-2 Container container 成员变量-3 CodecContext codec_context 成员变量-4 dict metadata Property id Property profile Property index Property average_rate Property base_rate Property guessed_rate Property start_time Property duration Property frames(The number of frames this stream contains.) Property language Property Type( Examples: 'audio'
, 'video'
, 'subtitle'
.) Fun encode() Fun decode() Fun get()/det() att
ContainerFormat 类: 这个类封装了ffmpeg的两个结构体:AVInputFormat
和AVOutputFormat
,在类里提供了可以访问该结构体的一些属性,主要在后面的container类中使用
序号 Value 成员变量-1 AVInputFormat *iptr 成员变量-2 AVOutputFormat *optr Property descriptor Property options Property input Property output Property is_input Property is_output Property long_name Property extensions Property flags 构造 self.iptr = lib.av_find_input_format(name)/self.optr = lib.av_guess_format(name, NULL, NULL) 全局函数 全局函数 get_output_format_names() 获取所有ffmpeg支持的output names 全局函数 get_input_format_names()获取所有ffmpeg支持的input names
Container 类:基类
序号 Value 成员变量-1 AVFormatContext *ptr 成员变量-2 dict options 成员变量-3 dict container_options 成员变量-4 list stream_options 成员变量-5 StreamContainer streams 成员变量-6 open_timeout 成员变量-7 read_timeout 成员变量-8 io_open Fun dumps_format() Fun set_timeout() self.interrupt_callback_info.timeout = timeout Fun start_timeout() self.interrupt_callback_info.start_time = clock() 构造函数 avformat_open_input() 析构函数 avformat_free_context(self.ptr) 全局函数 open()根据flag是r还是w选择return InputContainer()/ return OutputContainer()
InputContainer(Container) 类: InputContainer 这里有个创建codec的步骤,特别要注意,并且这里是采用avcodec_find_decoder()
意味着你无法在这里选择合适的解码器,所以我们上面第三个案例中采用了demux和单独codec create()的做法
序号 Value 成员变量-1 AVStream *ptr 成员变量-2 Container container Fun demux()在里面对stream赋值packet._stream = self.streams[packet.ptr.stream_index],stream中包含了py_codec_context Fun decode() 实际使用的是 packet.decode() Fun seek() 构造函数 avformat_find_stream_info()->avcodec_find_decoder()->avcodec_alloc_context3() 析构函数 avformat_close_input()
OutputContainer(Container) 类:
序号 Value 成员变量-1 bint _started 成员变量-2 _done Fun add_stream() 这里创建一个输出流avformat_new_stream()->创建编码器CodecContext py_codec_context = wrap_codec_context(codec_context, codec),注意这里的py_codec_context赋给stream中的相关变量,用来后期encode() Fun mux() av_interleaved_write_frame(self.ptr, self.packet_ptr)
Codec 类: 该类主要封装了AVCodec
,提供了相关获取的属性
序号 Value 成员变量-1 AVCodec *ptr 成员变量-2 AVCodecDescriptor *desc Property is_decoder Property descriptor Property name Property type Property frame_rates Property audio_rates Property video_formats Property audio_formats Fun create() 工厂函数return CodecContext.create(self) 构造函数 avcodec_find_encoder_by_name() /avcodec_find_decoder_by_name根据mode是w/r选择解码器或者编码器 析构函数 全局函数 cdef Codec wrap_codec(const lib.AVCodec *ptr) 全局函数 get_codec_names() 所有ffmpeg支持的codec names 全局函数 dump_codecs() ““Print information about available codecs.””
CodecContext 类:
序号 Value 成员变量-1 AVCodecContext *ptr 成员变量-2 bint extradata_set 成员变量-3 int stream_index 成员变量-4 Codec codec 成员变量-5 dict options Property flags Property extradata set/get att 参见第三个sample中的设置 Property extradata_size Property is_open Property is_encoder Property is_decoder Property skip_frame Property thread_type Property thread_count Property bit_rate_tolerance Property max_bit_rate Property bit_rate Property ticks_per_frame Property codec_tag Property time_base Property profile Property name Fun create()工厂函数 Fun open() Fun close() Fun encode()这里会调用一次self.open(strict=False),所以open()可以不用显示调用 Fun decode() 这里会调用一次self.open(strict=False),所以open()可以不用显示调用 Fun parse 里面调用的是av_parser_parse2()
构造函数 self.codec = wrap_codec() ->self.options = {} 这个options在第一个decode()调用之前可以设置,具体参见第三个sample 析构函数 lib.avcodec_close(self.ptr)/lib.avcodec_free_context(&self.ptr) 全局函数 CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*)
VideoCodecContext (CodecContext) 类: 该类也没做什么,主要对外提供属性接口
序号 Value 成员变量-1 VideoFormat _format 成员变量-2 VideoReformatter reformatter 成员变量-3 int encoded_frame_count (for encoding) 成员变量-4 VideoFrame next_frame (for decoding) Property format Property width Property height Property pix_fmt Property framerate Property rate Property gop_size Property sample_aspect_ratio Property display_aspect_ratio Property has_b_frames Property coded_width Property coded_height Fun _build_format()
VideoReformatter 类: 主要进行sws_cale的操作
序号 Value 成员变量-1 SwsContext *ptr Fun reformat()
其它辅助类: VideoFormat 类: 该类主要封装了AVPixelFormat
和AVPixFmtDescriptor
序号 Value 成员变量-1 AVPixelFormat pix_fmt 成员变量-2 AVPixFmtDescriptor *ptr Property name Property bits_per_pixel Property padded_bits_per_pixel Property is_big_endian Property has_palette Property is_bit_stream Property is_planar Property is_rgb Fun chroma_width Fun chroma_height 构造 self.pix_fmt = pix_fmt ,self.ptr = lib.av_pix_fmt_desc_get(pix_fmt)
PyIOFile 类:
序号 Value 成员变量-1 AVIOContext *iocontext 成员变量-2 unsigned char *buffer 成员变量-3 long pos Property fread Property fwrite Property fseek Property ftell Property fclose Fun pyio_read() Fun pyio_write() Fun pyio_seek() Fun Fun