街猫自研多媒体能力介绍

街猫自研多媒体能力介绍

首页角色扮演网络街更新时间:2024-05-29

背景

哈啰街猫移动团队在支撑业务发展过程中,已有的多媒体基础能力存在一些问题/瓶颈:

所以需要自研多媒体框架去解决/优化上述问题,以便后续能够更好的支撑业务发展。

街猫自研多媒体架构

硬编解码支持

Android 7.0以后,将media codec部分从media player service里抽离出来,单独开了一条新的binder服务media.codec来开放系统的硬编解码能力,系统要开放,必须要有标准,让各个硬件平台根据标准来开发其硬编解码器,然后集成到media.codec中,这个协议就是OpenMax。

OpenMax分为三层:

OpenMax是多媒体框架标准,目前应用最广泛的是IL层,各个硬件平台只需要依托IL层协议,提供统一的抽象层接口,屏蔽各自在底层适配时存在的差异,最终打包到libstagefrighthw.so,由media codec service加载后,以binder服务的形式,将硬编解码能力开放给有需要的多媒体应用,包括OpenMax AL,FFMPEG,nuplayer,exoplayer,街猫多媒体组件等等。

多媒体底层框架 - FFMPEG

街猫多媒体底层依托于ffmpeg,从而具备了覆盖多媒体全应用场景的底层能力,基于ffMPEG 4.2.2源码,我们目前主要做了如下定制化:

自研多媒体组件

组件名称

描述

街猫多媒体核心组件

包含街猫多媒体基础java和c 代码实现, 包含两个核心的c 库:1. libpet-media-core.so 包含ffmpeg4.2.2的代码和街猫多媒体转码,直播,视频合成的核心实现(平台无关),2. libpet-media-compat.so android和ios的代理库,包含android和ios平台兼容性c 实现

街猫多媒体转码组件

依赖多媒体核心组件,包含转码的java层实现核心能力:* 支持全格式转码,优先使用硬编码,如果设备不支持,则自动降级为软编码 * 支持码率,分辨率,fps,gop等参数配置 * 支持对源视频指定区间转码 * 转码后视频编码格式默认为h264,音频aac * 转码完成后,对生成视频做有效性校验,确保转码符合要求 * 转码后MP4视频全部moov前置

街猫多媒体直播组件

依赖多媒体核心组件,包含flv直播流的java层实现,核心能力:* 支持flv hevc格式的直播流的播放,支持软硬解码动态切换,相对三方库纯硬解码,具有更好的兼容性, * api的设计跟三方sdk完全保持一致,业务层无缝接入

街猫多媒体视频合成组件

依赖多媒体核心组件,包含音视频合成的java层实现, 核心能力:* 支持输入视频和音频数据流,合成h264编码格式的mp4文件 * 视频格式支持yuv420p&nv12(格式可扩展) * 音频输入pcm数据,支持Packed和Planar两种格式,也可不设置音频(合成视频无声音) * 支持添加logo和名称水印滤镜(滤镜可扩展) * 支持配置合成视频的片尾视频 * 合成视频的编码格式,码率、软硬编码等可配置

业务成果

街猫转码

街猫直播

街猫合成

FFMPEG介绍

核心库

音视频播放流程

上图是视频播放器的基本流程,source、demux、decoder、output

市面上绝大多数播放器的基本结构都是如此,不同的是在实现方式上会存在差异。

一个简单的FFMPEG工程

下面拿ffmpeg/examples/transcoding.c做介绍,这是一个转码的参考工程:

#include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavutil/opt.h> #include <libavutil/pixdesc.h> static AVFormatContext *ifmt_ctx; static AVFormatContext *ofmt_ctx; typedef struct FilteringContext { AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; AVFilterGraph *filter_graph; } FilteringContext; static FilteringContext *filter_ctx; typedef struct StreamContext { AVCodecContext *dec_ctx; AVCodecContext *enc_ctx; } StreamContext; static StreamContext *stream_ctx; static int open_input_file(const char *filename) { int ret; unsigned int i; ifmt_ctx = NULL; if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n"); return ret; } if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n"); return ret; } stream_ctx = av_mallocz_array(ifmt_ctx->nb_streams, sizeof(*stream_ctx)); if (!stream_ctx) return AVERROR(ENOMEM); for (i = 0; i < ifmt_ctx->nb_streams; i ) { AVStream *stream = ifmt_ctx->streams[i]; AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id); AVCodecContext *codec_ctx; if (!dec) { av_log(NULL, AV_LOG_ERROR, "Failed to find decoder for stream #%u\n", i); return AVERROR_DECODER_NOT_FOUND; } codec_ctx = avcodec_alloc_context3(dec); if (!codec_ctx) { av_log(NULL, AV_LOG_ERROR, "Failed to allocate the decoder context for stream #%u\n", i); return AVERROR(ENOMEM); } ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to copy decoder parameters to input decoder context " "for stream #%u\n", i); return ret; } /* Reencode video & audio and remux subtitles etc. */ if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, stream, NULL); /* Open decoder */ ret = avcodec_open2(codec_ctx, dec, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i); return ret; } } stream_ctx[i].dec_ctx = codec_ctx; } av_dump_format(ifmt_ctx, 0, filename, 0); return 0; } static int open_output_file(const char *filename) { AVStream *out_stream; AVStream *in_stream; AVCodecContext *dec_ctx, *enc_ctx; AVCodec *encoder; int ret; unsigned int i; ofmt_ctx = NULL; avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename); if (!ofmt_ctx) { av_log(NULL, AV_LOG_ERROR, "Could not create output context\n"); return AVERROR_UNKNOWN; } for (i = 0; i < ifmt_ctx->nb_streams; i ) { out_stream = avformat_new_stream(ofmt_ctx, NULL); if (!out_stream) { av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n"); return AVERROR_UNKNOWN; } in_stream = ifmt_ctx->streams[i]; dec_ctx = stream_ctx[i].dec_ctx; if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { /* in this example, we choose transcoding to same codec */ encoder = avcodec_find_encoder(dec_ctx->codec_id); if (!encoder) { av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n"); return AVERROR_INVALIDDATA; } enc_ctx = avcodec_alloc_context3(encoder); if (!enc_ctx) { av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n"); return AVERROR(ENOMEM); } /* In this example, we transcode to same properties (picture size, * sample rate etc.). These properties can be changed for output * streams easily using filters */ if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { enc_ctx->height = dec_ctx->height; enc_ctx->width = dec_ctx->width; enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; /* take first format from list of supported formats */ if (encoder->pix_fmts) enc_ctx->pix_fmt = encoder->pix_fmts[0]; else enc_ctx->pix_fmt = dec_ctx->pix_fmt; /* video time_base can be set to whatever is handy and supported by encoder */ enc_ctx->time_base = av_inv_q(dec_ctx->framerate); } else { enc_ctx->sample_rate = dec_ctx->sample_rate; enc_ctx->channel_layout = dec_ctx->channel_layout; enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); /* take first format from list of supported formats */ enc_ctx->sample_fmt = encoder->sample_fmts[0]; enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; } if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; /* Third parameter can be used to pass settings to encoder */ ret = avcodec_open2(enc_ctx, encoder, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); return ret; } ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i); return ret; } out_stream->time_base = enc_ctx->time_base; stream_ctx[i].enc_ctx = enc_ctx; } else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) { av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\n", i); return AVERROR_INVALIDDATA; } else { /* if this stream must be remuxed */ ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Copying parameters for stream #%u failed\n", i); return ret; } out_stream->time_base = in_stream->time_base; } } av_dump_format(ofmt_ctx, 0, filename, 1); if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename); return ret; } } /* init muxer, write output file header */ ret = avformat_write_header(ofmt_ctx, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n"); return ret; } return 0; } static int init_filter(FilteringContext* fctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec) { char args[512]; int ret = 0; const AVFilter *buffersrc = NULL; const AVFilter *buffersink = NULL; AVFilterContext *buffersrc_ctx = NULL; AVFilterContext *buffersink_ctx = NULL; AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); AVFilterGraph *filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { buffersrc = avfilter_get_by_name("buffer"); buffersink = avfilter_get_by_name("buffersink"); if (!buffersrc || !buffersink) { av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); ret = AVERROR_UNKNOWN; goto end; } snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den); ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); goto end; } ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); goto end; } ret = av_opt_set_bin(buffersink_ctx, "pix_fmts", (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt), AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); goto end; } } else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { buffersrc = avfilter_get_by_name("abuffer"); buffersink = avfilter_get_by_name("abuffersink"); if (!buffersrc || !buffersink) { av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); ret = AVERROR_UNKNOWN; goto end; } if (!dec_ctx->channel_layout) dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels); snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, av_get_sample_fmt_name(dec_ctx->sample_fmt), dec_ctx->channel_layout); ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n"); goto end; } ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n"); goto end; } ret = av_opt_set_bin(buffersink_ctx, "sample_fmts", (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt), AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n"); goto end; } ret = av_opt_set_bin(buffersink_ctx, "channel_layouts", (uint8_t*)&enc_ctx->channel_layout, sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n"); goto end; } ret = av_opt_set_bin(buffersink_ctx, "sample_rates", (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate), AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n"); goto end; } } else { ret = AVERROR_UNKNOWN; goto end; } /* Endpoints for the filter graph. */ outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL; if (!outputs->name || !inputs->name) { ret = AVERROR(ENOMEM); goto end; } if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec, &inputs, &outputs, NULL)) < 0) goto end; if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end; /* Fill FilteringContext */ fctx->buffersrc_ctx = buffersrc_ctx; fctx->buffersink_ctx = buffersink_ctx; fctx->filter_graph = filter_graph; end: avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; } static int init_filters(void) { const char *filter_spec; unsigned int i; int ret; filter_ctx = av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx)); if (!filter_ctx) return AVERROR(ENOMEM); for (i = 0; i < ifmt_ctx->nb_streams; i ) { filter_ctx[i].buffersrc_ctx = NULL; filter_ctx[i].buffersink_ctx = NULL; filter_ctx[i].filter_graph = NULL; if (!(ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) continue; if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) filter_spec = "null"; /* passthrough (dummy) filter for video */ else filter_spec = "anull"; /* passthrough (dummy) filter for audio */ ret = init_filter(&filter_ctx[i], stream_ctx[i].dec_ctx, stream_ctx[i].enc_ctx, filter_spec); if (ret) return ret; } return 0; } static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) { int ret; int got_frame_local; AVPacket enc_pkt; int (*enc_func)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2; if (!got_frame) got_frame = &got_frame_local; av_log(NULL, AV_LOG_INFO, "Encoding frame\n"); /* encode filtered frame */ enc_pkt.data = NULL; enc_pkt.size = 0; av_init_packet(&enc_pkt); ret = enc_func(stream_ctx[stream_index].enc_ctx, &enc_pkt, filt_frame, got_frame); av_frame_free(&filt_frame); if (ret < 0) return ret; if (!(*got_frame)) return 0; /* prepare packet for muxing */ enc_pkt.stream_index = stream_index; av_packet_rescale_ts(&enc_pkt, stream_ctx[stream_index].enc_ctx->time_base, ofmt_ctx->streams[stream_index]->time_base); av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n"); /* mux encoded frame */ ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt); return ret; } static int filter_encode_write_frame(AVFrame *frame, unsigned int stream_index) { int ret; AVFrame *filt_frame; av_log(NULL, AV_LOG_INFO, "Pushing decoded frame to filters\n"); /* push the decoded frame into the filtergraph */ ret = av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx, frame, 0); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n"); return ret; } /* pull filtered frames from the filtergraph */ while (1) { filt_frame = av_frame_alloc(); if (!filt_frame) { ret = AVERROR(ENOMEM); break; } av_log(NULL, AV_LOG_INFO, "Pulling filtered frame from filters\n"); ret = av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx, filt_frame); if (ret < 0) { /* if no more frames for output - returns AVERROR(EAGAIN) * if flushed and no more frames for output - returns AVERROR_EOF * rewrite retcode to 0 to show it as normal procedure completion */ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ret = 0; av_frame_free(&filt_frame); break; } filt_frame->pict_type = AV_PICTURE_TYPE_NONE; ret = encode_write_frame(filt_frame, stream_index, NULL); if (ret < 0) break; } return ret; } static int flush_encoder(unsigned int stream_index) { int ret; int got_frame; if (!(stream_ctx[stream_index].enc_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) return 0; while (1) { av_log(NULL, AV_LOG_INFO, "Flushing stream #%u encoder\n", stream_index); ret = encode_write_frame(NULL, stream_index, &got_frame); if (ret < 0) break; if (!got_frame) return 0; } return ret; } int main(int argc, char **argv) { int ret; AVPacket packet = { .data = NULL, .size = 0 }; AVFrame *frame = NULL; enum AVMediaType type; unsigned int stream_index; unsigned int i; int got_frame; int (*dec_func)(AVCodecContext *, AVFrame *, int *, const AVPacket *); if (argc != 3) { av_log(NULL, AV_LOG_ERROR, "Usage: %s <input file> <output file>\n", argv[0]); return 1; } if ((ret = open_input_file(argv[1])) < 0) goto end; if ((ret = open_output_file(argv[2])) < 0) goto end; if ((ret = init_filters()) < 0) goto end; /* read all packets */ while (1) { if ((ret = av_read_frame(ifmt_ctx, &packet)) < 0) break; stream_index = packet.stream_index; type = ifmt_ctx->streams[packet.stream_index]->codecpar->codec_type; av_log(NULL, AV_LOG_DEBUG, "Demuxer gave frame of stream_index %u\n", stream_index); if (filter_ctx[stream_index].filter_graph) { av_log(NULL, AV_LOG_DEBUG, "Going to reencode&filter the frame\n"); frame = av_frame_alloc(); if (!frame) { ret = AVERROR(ENOMEM); break; } av_packet_rescale_ts(&packet, ifmt_ctx->streams[stream_index]->time_base, stream_ctx[stream_index].dec_ctx->time_base); dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 : avcodec_decode_audio4; ret = dec_func(stream_ctx[stream_index].dec_ctx, frame, &got_frame, &packet); if (ret < 0) { av_frame_free(&frame); av_log(NULL, AV_LOG_ERROR, "Decoding failed\n"); break; } if (got_frame) { frame->pts = frame->best_effort_timestamp; ret = filter_encode_write_frame(frame, stream_index); av_frame_free(&frame); if (ret < 0) goto end; } else { av_frame_free(&frame); } } else { /* remux this frame without reencoding */ av_packet_rescale_ts(&packet, ifmt_ctx->streams[stream_index]->time_base, ofmt_ctx->streams[stream_index]->time_base); ret = av_interleaved_write_frame(ofmt_ctx, &packet); if (ret < 0) goto end; } av_packet_unref(&packet); } /* flush filters and encoders */ for (i = 0; i < ifmt_ctx->nb_streams; i ) { /* flush filter */ if (!filter_ctx[i].filter_graph) continue; ret = filter_encode_write_frame(NULL, i); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Flushing filter failed\n"); goto end; } /* flush encoder */ ret = flush_encoder(i); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n"); goto end; } } av_write_trailer(ofmt_ctx); end: av_packet_unref(&packet); av_frame_free(&frame); for (i = 0; i < ifmt_ctx->nb_streams; i ) { avcodec_free_context(&stream_ctx[i].dec_ctx); if (ofmt_ctx && ofmt_ctx->nb_streams > i && ofmt_ctx->streams[i] && stream_ctx[i].enc_ctx) avcodec_free_context(&stream_ctx[i].enc_ctx); if (filter_ctx && filter_ctx[i].filter_graph) avfilter_graph_free(&filter_ctx[i].filter_graph); } av_free(filter_ctx); av_free(stream_ctx); avformat_close_input(&ifmt_ctx); if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) avio_closep(&ofmt_ctx->pb); avformat_free_context(ofmt_ctx); if (ret < 0) av_log(NULL, AV_LOG_ERROR, "Error occurred: %s\n", av_err2str(ret)); return ret ? 1 : 0; }

代码核心流程介绍:

帧数据存储

ffmpeg使用AVPacket来存储编码的帧数据,解码后的音视频帧数据,统一使用AVFrame来存储。

音频帧数据存储

音频解码后的pcm数据,在ffmpeg内部有Packed和Planar两种存储方式:

Packed格式,frame.data[0]或frame.extended_data[0]包含AVFrame保存的所有pcm数据

Planar格式,frame.data[i]或者frame.extended_data[i]表示第i个声道的数据

AVFrame.data数组大小固定为8,如果声道数超过8,需要从frame.extended_data获取声道数据, extended_data是为了支持更多的声道数,后期扩展的字段。

Planar是ffmpeg内部的数据格式,常规的都为Packed,从命名上,Planar一般都在Packed命名后 P,比如sample format为16bit,Packed命名:AV_SAMPLE_FMT_S16,Planar:AV_SAMPLE_FMT_S16P。

除了data/extended_data,AVFrame保存音频数据其他四个核心字段:format(AVSampleFormat),sample_rate,channel_layout, nb_samples。

format - 指的是单帧的存储格式,可以通过该值来确定是packed还是planar,以及存储大小,16bit或float等

sample_rate - 采样率

channel_layout - 声道数

nb_samples - AVFrame中包含的采样数(单声道)

所以,我们通过sample_rate和nb_samples就可以得出AVFrame包含pcm数据的播放时长,通过format * channel_layout * nb_samples就可以得出AVFrame中buffer的长度

**视频帧数据存储
**视频帧数据采用YUV格式,其中Y指亮度通道,UV指色度通道,主流的采样格式有:YUV444、YUV422、YUV420

YUV4:*:*, 简单点理解就是,以4个像素为一采样组,每个像素固定有一个Y通道,YUV后面UV对应的数值,以2为单位对应一组UV色度通道,注意,UV是一组,不要将其分开,基于这个去理解YUV4:2:2和YUV4:2:0采样方式,会更容易点

对于大分辨率的视频帧,相邻像素的色度通道差异是极小的,所以YUV420格式在保证图像质量的情况下,又大幅的降低了存储,是目前主流的帧格式,接下去重点介绍下YUV420在ffmpeg,即AVFrame里的存储,

YUV420根据Y,U,V数据的存储方式,又细分出YUV420P,YUV420SP(NV12)等子格式

YUV420P: YYYYYYYY UU VV

Y分量、U分量、V分量分别占一个平面空间,4个像素的Y分量共用一个UV分量

YUV420SP: YYYYYYYY UU VV

Y分量占一个平面空间,UV交差存储占一个平面空间,4个像素的Y分量共用一个UV分量

二者的差异,就是UV分量的存储方式,AVFrame保存YUV数据,主要用

uint8_t *data[AV_NUM_DATA_POINTERS]; int linesize[AV_NUM_DATA_POINTERS];

data保存平面对应的向量数据,linesize保存平面对应向量数据的长度,所以yuv420p有三个平面,data数组有效长度为3,yuv420sp只有两个平面,有效长度就只有2。

时间基 - TimeBase

ffmpeg音视频处理,有一个很重要的概念,那就是时间基,时间基本质是时间刻度,ffmpeg内部用到的时间戳都是要与对应的time base换算才能拿到准确时间的,ffmpeg内部主要有三种类型的time base

有了时间基和时间戳,可以很容易计算出对应的时间,比如时间戳50,时间基1/25

50 * 1 / 25 = 2s

ffmpeg也提供了辅助计算函数:

timestamp(秒) = pts * av_q2d(time_base)

不同时间基之间换算:

av_rescale_q

对AVPacket内部时间基的换算:

av_packet_rescale_ts

滤镜 - Filter

ffmpeg内部filter graph处理流程

filter graph建立后,会监听source filter的source buffer,如果有AVFrame塞入,filter graph就开始运作,通过filter chain处理完后,从sink filter的sink buffer中取出,格式为AVFrame。

上述工程里initFilter代码流程:

然后在帧处理的时候:

步骤2和3创建了source和sink filter,work filter则是使用avfilter_graph_parse_ptr传入filter spec,ffmpeg会解析spec创建内置的filter,当然,我们也可以实现自定义的filter塞入到filte graph中。

下面是添加水印的filter spec:

static const char* filters[] = { "movie=/sdcard/0/pet_logo.png[watermark];[in][watermark]overlay=main_w-overlay_w-10:main_h-overlay_h-10[out]"};

filter spec更多介绍,可以去官网查看:https://ffmpeg.org/ffmpeg-filters.html

作者:胡付义

来源:微信公众号:哈啰技术

出处:https://mp.weixin.qq.com/s/mh243Ogpvw2dkatlEvgERQ

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved