Pyav代码分析

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




# 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"}
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,)

    # We wait until the end to bail so that the last empty `buf` flushes
    # the parser.
    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.options={"device_id":"0"}
codec.open()
print(codec.name)
# print(codec.extradata_size)
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;
        }
//这里就是最后循环取出解码器中的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
成员变量-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类中还包含了两个其它的类:ContainerCodecContext

序号 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的两个结构体:AVInputFormatAVOutputFormat ,在类里提供了可以访问该结构体的一些属性,主要在后面的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 类:
该类主要封装了AVPixelFormatAVPixFmtDescriptor

序号 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

你可能感兴趣的:(python)