在上一篇文章中,咱们了解了音频的解码和播映进程,这篇咱们一起来看一下ijk播映器在Android平台上视频是如何进行解码和播映的。在Android上ijk支持视频的软件和硬解,咱们先看一下软解及其播映进程。
1.线程创立
其实在之前的文章里边现已贴过视频烘托线程和视频解码线程的创立相关的代码了,但为了完好的流程,这儿仍是贴一下。
视频烘托线程创立
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
//创立video改写线程
is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
}
// 烘托线程进口函数
static int video_refresh_thread(void *arg)
{
FFPlayer *ffp = arg;
VideoState *is = ffp->is;
double remaining_time = 0.0;
while (!is->abort_request) {
if (remaining_time > 0.0)
av_usleep((int)(int64_t)(remaining_time * 1000000.0));
remaining_time = REFRESH_RATE;
if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
video_refresh(ffp, &remaining_time);
}
return 0;
}
视频解码线程创立
static int stream_component_open(FFPlayer *ffp, int stream_index)
{
..........
case AVMEDIA_TYPE_VIDEO:
is->video_stream = stream_index;
is->video_st = ic->streams[stream_index];
if (ffp->async_init_decoder) {
while (!is->initialized_decoder) {
SDL_Delay(5);
}
if (ffp->node_vdec) {
is->viddec.avctx = avctx;
ret = ffpipeline_config_video_decoder(ffp->pipeline, ffp);
}
if (ret || !ffp->node_vdec) {
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);
if (!ffp->node_vdec)
goto fail;
}
} else {
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
//初始化解码器,假如支持硬解走硬解,否则运用软解
ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);
if (!ffp->node_vdec)
goto fail;
}
//创立video解码线程
if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0)
goto out;
}
// 视频解码线程进口函数
static int video_thread(void *arg)
{
FFPlayer *ffp = (FFPlayer *)arg;
int ret = 0;
if (ffp->node_vdec) {
ret = ffpipenode_run_sync(ffp->node_vdec);
}
return ret;
}
解码相关接口装备
IJKFF_Pipenode* ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
return pipeline->func_open_video_decoder(pipeline, ffp);
}
static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
IJKFF_Pipenode *node = NULL;
//优先运用硬解码器,会先查看视频流格局是否契合,不不契合或者没有敞开硬解码,运用软解码
if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2)
node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);
if (!node) {
node = ffpipenode_create_video_decoder_from_ffplay(ffp);
}
return node;
}
这儿咱们先看软解码所装备的接口。
IJKFF_Pipenode *ffpipenode_create_video_decoder_from_ffplay(FFPlayer *ffp)
{
IJKFF_Pipenode *node = ffpipenode_alloc(sizeof(IJKFF_Pipenode_Opaque));
if (!node)
return node;
IJKFF_Pipenode_Opaque *opaque = node->opaque;
opaque->ffp = ffp;
node->func_destroy = func_destroy;
node->func_run_sync = func_run_sync;
ffp_set_video_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(ffp->is->viddec.avctx->codec_id));
ffp->stat.vdec_type = FFP_PROPV_DECODER_AVCODEC;
return node;
}
2.视频解码
履行之前装备的接口。
static int video_thread(void *arg)
{
FFPlayer *ffp = (FFPlayer *)arg;
int ret = 0;
if (ffp->node_vdec) {
ret = ffpipenode_run_sync(ffp->node_vdec);
}
return ret;
}
下面是软解码的进程,视频的软解码与音频的类似,都是调用decoder_decode_frame
获取AVFrame
,然后再放入到pictq
中等待被烘托。
static int func_run_sync(IJKFF_Pipenode *node)
{
IJKFF_Pipenode_Opaque *opaque = node->opaque;
return ffp_video_thread(opaque->ffp);
}
int ffp_video_thread(FFPlayer *ffp)
{
return ffplay_video_thread(ffp);
}
static int ffplay_video_thread(void *arg)
{
FFPlayer *ffp = arg;
VideoState *is = ffp->is;
AVFrame *frame = av_frame_alloc();
double pts;
double duration;
int ret;
AVRational tb = is->video_st->time_base;
AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
int64_t dst_pts = -1;
int64_t last_dst_pts = -1;
int retry_convert_image = 0;
int convert_frame_count = 0;
ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, ffp_get_video_rotate_degrees(ffp));
if (!frame) {
return AVERROR(ENOMEM);
}
for (;;) {
//填充frame
ret = get_video_frame(ffp, frame);
if (ret < 0)
goto the_end;
if (!ret)
continue;
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
//添加到帧行列
ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
av_frame_unref(frame);
if (ret < 0)
goto the_end;
}
the_end:
av_log(NULL, AV_LOG_INFO, "convert image convert_frame_count = %d\n", convert_frame_count);
av_frame_free(&frame);
return 0;
}
获取视频帧
static int get_video_frame(FFPlayer *ffp, AVFrame *frame)
{
VideoState *is = ffp->is;
int got_picture;
ffp_video_statistic_l(ffp);
//履行avcodec_send_packet和avcodec_receive_frame,获取解码后的frame
if ((got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)) < 0)
return -1;
if (got_picture) {
double dpts = NAN;
if (frame->pts != AV_NOPTS_VALUE)
dpts = av_q2d(is->video_st->time_base) * frame->pts;
frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
}
return got_picture;
}
添加到视频帧行列
在添加到帧行列前还需求做两件工作:
1.假如从行列中取出的可写Frame
还未分配过或画面宽高发生变化需求为其重新分配SDL_VoutOverlay
。
2.转化为方针像素格局,并填充到Frame
中。
static int queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
{
VideoState *is = ffp->is;
Frame *vp;
int video_accurate_seek_fail = 0;
int64_t video_seek_pos = 0;
int64_t now = 0;
int64_t deviation = 0;
int64_t deviation2 = 0;
int64_t deviation3 = 0;
//从pictq中取出可写frame
if (!(vp = frame_queue_peek_writable(&is->pictq)))
return -1;
vp->sar = src_frame->sample_aspect_ratio;
/* alloc or resize hardware picture buffer */
if (!vp->bmp || !vp->allocated ||
vp->width != src_frame->width ||
vp->height != src_frame->height ||
vp->format != src_frame->format) {
if (vp->width != src_frame->width || vp->height != src_frame->height)
ffp_notify_msg3(ffp, FFP_MSG_VIDEO_SIZE_CHANGED, src_frame->width, src_frame->height);
//设置格局宽高
vp->allocated = 0;
vp->width = src_frame->width;
vp->height = src_frame->height;
vp->format = src_frame->format;
/* the allocation must be done in the main thread to avoid
locking problems. */
//分配picture
alloc_picture(ffp, src_frame->format);
if (is->videoq.abort_request)
return -1;
}
/* if the frame is not skipped, then display it */
if (vp->bmp) {
/* get a pointer on the bitmap */
SDL_VoutLockYUVOverlay(vp->bmp);
// FIXME: set swscale options
// 进行像素数据填充
if (SDL_VoutFillFrameYUVOverlay(vp->bmp, src_frame) < 0) {
av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");
exit(1);
}
/* update the bitmap content */
SDL_VoutUnlockYUVOverlay(vp->bmp);
vp->pts = pts;
vp->duration = duration;
vp->pos = pos;
vp->serial = serial;
vp->sar = src_frame->sample_aspect_ratio;
vp->bmp->sar_num = vp->sar.num;
vp->bmp->sar_den = vp->sar.den;
//添加可读frame
//至此咱们能拿到烘托一帧画面的数据
frame_queue_push(&is->pictq);
if (!is->viddec.first_frame_decoded) {
ALOGD("Video: first frame decoded\n");
ffp_notify_msg1(ffp, FFP_MSG_VIDEO_DECODED_START);
is->viddec.first_frame_decoded_time = SDL_GetTickHR();
is->viddec.first_frame_decoded = 1;
}
}
return 0;
}
分配SDL_VoutOverlay
在ijk中默许是运用软解码,显现的格局默以为SDL_FCC_RV32
,并运用ANativeWindow复制像素方式完成视频的制作。
static void alloc_picture(FFPlayer *ffp, int frame_format)
{
av_log(NULL, AV_LOG_WARNING, "hyc alloc_picture frame_format=%d",frame_format);
VideoState *is = ffp->is;
Frame *vp;
vp = &is->pictq.queue[is->pictq.windex];
// 手动铲除,软件制作没有做清理操作
free_picture(vp);
//设置SDL_Vout 的format即需求显现的格局默以为SDL_FCC_RV32
SDL_VoutSetOverlayFormat(ffp->vout, ffp->overlay_format);
//创立Overlay
vp->bmp = SDL_Vout_CreateOverlay(vp->width, vp->height,
frame_format,
ffp->vout);
SDL_LockMutex(is->pictq.mutex);
vp->allocated = 1;
SDL_CondSignal(is->pictq.cond);
SDL_UnlockMutex(is->pictq.mutex);
}
static SDL_VoutOverlay *func_create_overlay(int width, int height, int frame_format, SDL_Vout *vout)
{
SDL_LockMutex(vout->mutex);
SDL_VoutOverlay *overlay = func_create_overlay_l(width, height, frame_format, vout);
SDL_UnlockMutex(vout->mutex);
return overlay;
}
static SDL_VoutOverlay *func_create_overlay_l(int width, int height, int frame_format, SDL_Vout *vout)
{
//在获取frame的时候设置 frame_format,硬解码为IJK_AV_PIX_FMT__ANDROID_MEDIACODEC
switch (frame_format) {
case IJK_AV_PIX_FMT__ANDROID_MEDIACODEC:
return SDL_VoutAMediaCodec_CreateOverlay(width, height, vout);
default:
return SDL_VoutFFmpeg_CreateOverlay(width, height, frame_format, vout);
}
}
创立SDL_VoutOverlay
时,依据需求显现的像素类型,去挑选FFmpeg中的对应的像素格局,并分配好包括这种像素格局的AVFrame
.
SDL_VoutOverlay *SDL_VoutFFmpeg_CreateOverlay(int width, int height, int frame_format, SDL_Vout *display)
{
//需求显现的格局
Uint32 overlay_format = display->overlay_format;
switch (overlay_format) {
// 运用opengl进行烘托
//依据像素格局重新设置overlay_format格局
case SDL_FCC__GLES2: {
switch (frame_format) {
case AV_PIX_FMT_YUV444P10LE:
overlay_format = SDL_FCC_I444P10LE;
break;
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
default:
#if defined(__ANDROID__)
overlay_format = SDL_FCC_YV12;
#else
overlay_format = SDL_FCC_I420;
#endif
break;
}
break;
}
}
SDL_VoutOverlay *overlay = SDL_VoutOverlay_CreateInternal(sizeof(SDL_VoutOverlay_Opaque));
if (!overlay) {
ALOGE("overlay allocation failed");
return NULL;
}
//填充SDL_VoutOverlay_Opaque的数据
SDL_VoutOverlay_Opaque *opaque = overlay->opaque;
opaque->mutex = SDL_CreateMutex();
opaque->sws_flags = SWS_BILINEAR;
overlay->opaque_class = &g_vout_overlay_ffmpeg_class;
overlay->format = overlay_format;
overlay->pitches = opaque->pitches;
overlay->pixels = opaque->pixels;
overlay->w = width;
overlay->h = height;
overlay->free_l = func_free_l;
overlay->lock = func_lock;
overlay->unlock = func_unlock;
overlay->func_fill_frame = func_fill_frame;
enum AVPixelFormat ff_format = AV_PIX_FMT_NONE;
int buf_width = width;
int buf_height = height;
//依据overlay_format转化为FFmpeg中的像素格局和buf_width对齐
switch (overlay_format) {
case SDL_FCC_I420:
case SDL_FCC_YV12: {
//SDL_FCC_I420与SDL_FCC_YV12 相同,都属于yuv,y u v分别存储,4个y对应1个uv重量
// U、V 平面的 strides, width 和 height 都是 Y 平面的一半
//I420 现存u再存v,YV12 先存v再存u
ff_format = AV_PIX_FMT_YUV420P;
// FIXME: need runtime config
#if defined(__ANDROID__)
// 16 bytes align pitch for arm-neon image-convert
buf_width = IJKALIGN(width, 16); // 1 bytes per pixel for Y-plane
#elif defined(__APPLE__)
// 2^n align for width
buf_width = width;
if (width > 0)
buf_width = 1 << (sizeof(int) * 8 - __builtin_clz(width));
#else
buf_width = IJKALIGN(width, 16); // unknown platform
#endif
//存在3个重量
opaque->planes = 3;
break;
}
case SDL_FCC_I444P10LE: {
ff_format = AV_PIX_FMT_YUV444P10LE;
// FIXME: need runtime config
#if defined(__ANDROID__)
// 16 bytes align pitch for arm-neon image-convert
buf_width = IJKALIGN(width, 16); // 1 bytes per pixel for Y-plane
#elif defined(__APPLE__)
// 2^n align for width
buf_width = width;
if (width > 0)
buf_width = 1 << (sizeof(int) * 8 - __builtin_clz(width));
#else
buf_width = IJKALIGN(width, 16); // unknown platform
#endif
opaque->planes = 3;
break;
}
case SDL_FCC_RV16: {
ff_format = AV_PIX_FMT_RGB565;
buf_width = IJKALIGN(width, 8); // 2 bytes per pixel
opaque->planes = 1;
break;
}
case SDL_FCC_RV24: {
ff_format = AV_PIX_FMT_RGB24;
#if defined(__ANDROID__)
// 16 bytes align pitch for arm-neon image-convert
buf_width = IJKALIGN(width, 16); // 1 bytes per pixel for Y-plane
#elif defined(__APPLE__)
buf_width = width;
#else
buf_width = IJKALIGN(width, 16); // unknown platform
#endif
opaque->planes = 1;
break;
}
case SDL_FCC_RV32: {
ff_format = AV_PIX_FMT_0BGR32;
buf_width = IJKALIGN(width, 4); // 4 bytes per pixel
opaque->planes = 1;
break;
}
default:
ALOGE("SDL_VoutFFmpeg_CreateOverlay(...): unknown format %.4s(0x%x)\n", (char*)&overlay_format, overlay_format);
goto fail;
}
//分配对应格局的AVFrame
opaque->managed_frame = opaque_setup_frame(opaque, ff_format, buf_width, buf_height);
if (!opaque->managed_frame) {
ALOGE("overlay->opaque->frame allocation failed\n");
goto fail;
}
//进行指针赋值
overlay_fill(overlay, opaque->managed_frame, opaque->planes);
return overlay;
fail:
func_free_l(overlay);
return NULL;
}
分配AVFrame
在咱们取出带像素数据的AVFrame
后,还没办法立马将其烘托消费掉,可是代码里边是添加到行列之后就会进行回收操作,所以咱们需求对入行列的帧数据分配咱们自己的AVFrame
。在分配AVFrame
时会创立两个Frame方针,一种用于不需求转化直接从原始Frame中进行复制,一种则需求进行转化,并还需求进行Buffer的分配。
static AVFrame *opaque_setup_frame(SDL_VoutOverlay_Opaque* opaque, enum AVPixelFormat format, int width, int height)
{
// 需求转化像素时运用
AVFrame *managed_frame = av_frame_alloc();
if (!managed_frame) {
return NULL;
}
// 不需求转化像素时运用
AVFrame *linked_frame = av_frame_alloc();
if (!linked_frame) {
av_frame_free(&managed_frame);
return NULL;
}
/*-
* Lazily allocate frame buffer in opaque_obtain_managed_frame_buffer
*
* For refererenced frame management, we use buffer allocated by decoder
*
int frame_bytes = avpicture_get_size(format, width, height);
AVBufferRef *frame_buffer_ref = av_buffer_alloc(frame_bytes);
if (!frame_buffer_ref)
return NULL;
opaque->frame_buffer = frame_buffer_ref;
*/
managed_frame->format = format;
managed_frame->width = width;
managed_frame->height = height;
// 初始化managed_frame,未填充数据
av_image_fill_arrays(managed_frame->data, managed_frame->linesize ,NULL,
format, width, height, 1);
opaque->managed_frame = managed_frame;
opaque->linked_frame = linked_frame;
return managed_frame;
}
像素数据填充
在分配好SDL_VoutOverlay
后,还需求为其填充可烘托的像素数据。在这个进程中会依据解码后的AVFrame
的像素格局和方针像素格局进行转化。这儿假如运用OpenGL进行制作,而且像素格局是YUV420p的,那么能够不需求转化。假如不转化的话就运用方才分配好的linked_frame
,将需求烘托的帧数据直接链接曩昔。假如进行转化,那么还需求为managed_frame
分配好buffer。
static int func_fill_frame(SDL_VoutOverlay *overlay, const AVFrame *frame)
{
assert(overlay);
SDL_VoutOverlay_Opaque *opaque = overlay->opaque;
AVFrame swscale_dst_pic = { { 0 } };
av_frame_unref(opaque->linked_frame);
//获取需求展示的数据和当时的数据格局,进行像素格局转化
int need_swap_uv = 0;
int use_linked_frame = 0;
enum AVPixelFormat dst_format = AV_PIX_FMT_NONE;
//这儿假如运用OpenGL进行制作,而且像素格局是YUV420p的,那么能够不需求转化
switch (overlay->format) {
case SDL_FCC_YV12:
//vu需求转化为uv
need_swap_uv = 1;
// no break;
case SDL_FCC_I420:
if (frame->format == AV_PIX_FMT_YUV420P || frame->format == AV_PIX_FMT_YUVJ420P) {
// ALOGE("direct draw frame");
//不需求进行像素转化
use_linked_frame = 1;
dst_format = frame->format;
} else {
// ALOGE("copy draw frame");
dst_format = AV_PIX_FMT_YUV420P;
}
break;
case SDL_FCC_I444P10LE:
if (frame->format == AV_PIX_FMT_YUV444P10LE) {
// ALOGE("direct draw frame");
use_linked_frame = 1;
dst_format = frame->format;
} else {
// ALOGE("copy draw frame");
dst_format = AV_PIX_FMT_YUV444P10LE;
}
break;
case SDL_FCC_RV32:
dst_format = AV_PIX_FMT_0BGR32;
break;
case SDL_FCC_RV24:
dst_format = AV_PIX_FMT_RGB24;
break;
case SDL_FCC_RV16:
dst_format = AV_PIX_FMT_RGB565;
break;
default:
ALOGE("SDL_VoutFFmpeg_ConvertPicture: unexpected overlay format %s(%d)",
(char*)&overlay->format, overlay->format);
return -1;
}
// setup frame
//假如不需求转化像素格局
if (use_linked_frame) {
// linked frame
av_frame_ref(opaque->linked_frame, frame);
overlay_fill(overlay, opaque->linked_frame, opaque->planes);
//uv方位转化
if (need_swap_uv)
FFSWAP(Uint8*, overlay->pixels[1], overlay->pixels[2]);
} else {
//需求转化像素格局
// managed frame
AVFrame* managed_frame = opaque_obtain_managed_frame_buffer(opaque);
if (!managed_frame) {
ALOGE("OOM in opaque_obtain_managed_frame_buffer");
return -1;
}
overlay_fill(overlay, opaque->managed_frame, opaque->planes);
// setup frame managed
for (int i = 0; i < overlay->planes; ++i) {
swscale_dst_pic.data[i] = overlay->pixels[i];
swscale_dst_pic.linesize[i] = overlay->pitches[i];
}
if (need_swap_uv)
FFSWAP(Uint8*, swscale_dst_pic.data[1], swscale_dst_pic.data[2]);
}
// swscale / direct draw
/*
ALOGE("ijk_image_convert w=%d, h=%d, df=%d, dd=%d, dl=%d, sf=%d, sd=%d, sl=%d",
(int)frame->width,
(int)frame->height,
(int)dst_format,
(int)swscale_dst_pic.data[0],
(int)swscale_dst_pic.linesize[0],
(int)frame->format,
(int)(const uint8_t**) frame->data,
(int)frame->linesize);
*/
//假如需求转化先测验运用libyuv中进行yuv转rgb,假如转化失败,运用sws_scale进行转化
if (use_linked_frame) {
// do nothing
} else if (ijk_image_convert(frame->width, frame->height,
dst_format, swscale_dst_pic.data, swscale_dst_pic.linesize,
frame->format, (const uint8_t**) frame->data, frame->linesize)) {
opaque->img_convert_ctx = sws_getCachedContext(opaque->img_convert_ctx,
frame->width, frame->height, frame->format, frame->width, frame->height,
dst_format, opaque->sws_flags, NULL, NULL, NULL);
if (opaque->img_convert_ctx == NULL) {
ALOGE("sws_getCachedContext failed");
return -1;
}
sws_scale(opaque->img_convert_ctx, (const uint8_t**) frame->data, frame->linesize,
0, frame->height, swscale_dst_pic.data, swscale_dst_pic.linesize);
if (!opaque->no_neon_warned) {
opaque->no_neon_warned = 1;
ALOGE("non-neon image convert %s -> %s", av_get_pix_fmt_name(frame->format), av_get_pix_fmt_name(dst_format));
}
}
// TODO: 9 draw black if overlay is larger than screen
return 0;
}
分配buffer
static AVFrame *opaque_obtain_managed_frame_buffer(SDL_VoutOverlay_Opaque* opaque)
{
if (opaque->frame_buffer != NULL)
return opaque->managed_frame;
AVFrame *managed_frame = opaque->managed_frame;
int frame_bytes = av_image_get_buffer_size(managed_frame->format, managed_frame->width, managed_frame->height, 1);
// 为managed_frame分配buffer
AVBufferRef *frame_buffer_ref = av_buffer_alloc(frame_bytes);
if (!frame_buffer_ref)
return NULL;
av_image_fill_arrays(managed_frame->data, managed_frame->linesize,
frame_buffer_ref->data, managed_frame->format, managed_frame->width, managed_frame->height, 1);
opaque->frame_buffer = frame_buffer_ref;
return opaque->managed_frame;
}
将数据指向SDL_VoutOverlay
分配好了buffer之后,将SDL_VoutOverlay
的相关像素数据指向AVFrame
,后续就能够从这儿拿到像素数据了。
static void overlay_fill(SDL_VoutOverlay *overlay, AVFrame *frame, int planes)
{
overlay->planes = planes;
for (int i = 0; i < AV_NUM_DATA_POINTERS; ++i) {
overlay->pixels[i] = frame->data[i];
overlay->pitches[i] = frame->linesize[i];
}
}
像素转化
在android平台上,默许的方针像素是AV_PIX_FMT_0BGR32
,假如视频像素格局是YUV420P,那么运用libyuv进行转化。
int ijk_image_convert(int width, int height,
enum AVPixelFormat dst_format, uint8_t **dst_data, int *dst_linesize,
enum AVPixelFormat src_format, const uint8_t **src_data, const int *src_linesize)
{
#if defined(__ANDROID__)
switch (src_format) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P: // FIXME: 9 not equal to AV_PIX_FMT_YUV420P, but a workaround
switch (dst_format) {
case AV_PIX_FMT_RGB565:
return I420ToRGB565(
src_data[0], src_linesize[0],
src_data[1], src_linesize[1],
src_data[2], src_linesize[2],
dst_data[0], dst_linesize[0],
width, height);
case AV_PIX_FMT_0BGR32:
return I420ToABGR(
src_data[0], src_linesize[0],
src_data[1], src_linesize[1],
src_data[2], src_linesize[2],
dst_data[0], dst_linesize[0],
width, height);
default:
break;
}
break;
default:
break;
}
#endif
return -1;
}
3.视频烘托
这儿咱们依据上面的像素类型进行制作流程盯梢。烘托进程便是对SDL_VoutOverlay
中像素数据进行复制的进程。
static void video_image_display2(FFPlayer *ffp)
{
VideoState *is = ffp->is;
Frame *vp;
Frame *sp = NULL;
vp = frame_queue_peek_last(&is->pictq);
if (vp->bmp) {
if (ffp->render_wait_start && !ffp->start_on_prepared && is->pause_req) {
if (!ffp->first_video_frame_rendered) {
ffp->first_video_frame_rendered = 1;
ffp_notify_msg1(ffp, FFP_MSG_VIDEO_RENDERING_START);
}
//暂停
while (is->pause_req && !is->abort_request) {
SDL_Delay(20);
}
}
//履行制作
SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);
ffp->stat.vfps = SDL_SpeedSamplerAdd(&ffp->vfps_sampler, FFP_SHOW_VFPS_FFPLAY, "vfps[ffplay]");
if (!ffp->first_video_frame_rendered) {
ffp->first_video_frame_rendered = 1;
ffp_notify_msg1(ffp, FFP_MSG_VIDEO_RENDERING_START);
}
if (is->latest_video_seek_load_serial == vp->serial) {
int latest_video_seek_load_serial = __atomic_exchange_n(&(is->latest_video_seek_load_serial), -1, memory_order_seq_cst);
if (latest_video_seek_load_serial == vp->serial) {
ffp->stat.latest_seek_load_duration = (av_gettime() - is->latest_seek_load_start_at) / 1000;
if (ffp->av_sync_type == AV_SYNC_VIDEO_MASTER) {
ffp_notify_msg2(ffp, FFP_MSG_VIDEO_SEEK_RENDERING_START, 1);
} else {
ffp_notify_msg2(ffp, FFP_MSG_VIDEO_SEEK_RENDERING_START, 0);
}
}
}
}
}
目前overlay->format
是SDL_FCC_RV32
,而且vout->overlay_format
的值也是这个。所以上面的判别条件都不契合,会进入最后的Window制作流程。
int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay *overlay)
{
if (vout && overlay && vout->display_overlay)
return vout->display_overlay(vout, overlay);
return -1;
}
static int func_display_overlay(SDL_Vout *vout, SDL_VoutOverlay *overlay)
{
SDL_LockMutex(vout->mutex);
int retval = func_display_overlay_l(vout, overlay);
SDL_UnlockMutex(vout->mutex);
return retval;
}
static int func_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay)
{
SDL_Vout_Opaque *opaque = vout->opaque;
ANativeWindow *native_window = opaque->native_window;
if (!native_window) {
if (!opaque->null_native_window_warned) {
opaque->null_native_window_warned = 1;
ALOGW("func_display_overlay_l: NULL native_window");
}
return -1;
} else {
opaque->null_native_window_warned = 1;
}
if (!overlay) {
ALOGE("func_display_overlay_l: NULL overlay");
return -1;
}
if (overlay->w <= 0 || overlay->h <= 0) {
ALOGE("func_display_overlay_l: invalid overlay dimensions(%d, %d)", overlay->w, overlay->h);
return -1;
}
switch(overlay->format) {
case SDL_FCC__AMC: {
// only ANativeWindow support
IJK_EGL_terminate(opaque->egl);
//烘托为true就会烘托到surface
return SDL_VoutOverlayAMediaCodec_releaseFrame_l(overlay, NULL, true);
}
case SDL_FCC_RV24:
case SDL_FCC_I420:
case SDL_FCC_I444P10LE: {
// only GLES support
if (opaque->egl)
return IJK_EGL_display(opaque->egl, native_window, overlay);
break;
}
case SDL_FCC_YV12:
case SDL_FCC_RV16:
case SDL_FCC_RV32: {
// both GLES & ANativeWindow support
// 运用OpenGL进行制作
if (vout->overlay_format == SDL_FCC__GLES2 && opaque->egl)
return IJK_EGL_display(opaque->egl, native_window, overlay);
break;
}
}
// fallback to ANativeWindow
IJK_EGL_terminate(opaque->egl);
return SDL_Android_NativeWindow_display_l(native_window, overlay);
}
ANativeWindow制作
制作流程如下:
- 获取当时像素buffer所对应的像素格局,获取当时
ANativeWindow
对应的像素格局 - 判别巨细与格局是否相同,假如不同,那么调用
ANativeWindow_setBuffersGeometry
进行装备 - 依据方针像素格局将
SDL_VoutOverlay
的像素数据复制到ANativeWindow
中
int SDL_Android_NativeWindow_display_l(ANativeWindow *native_window, SDL_VoutOverlay *overlay)
{
int retval;
if (!native_window)
return -1;
if (!overlay) {
ALOGE("SDL_Android_NativeWindow_display_l: NULL overlay");
return -1;
}
if (overlay->w <= 0 || overlay->h <= 0) {
ALOGE("SDL_Android_NativeWindow_display_l: invalid overlay dimensions(%d, %d)", overlay->w, overlay->h);
return -1;
}
int curr_w = ANativeWindow_getWidth(native_window);
int curr_h = ANativeWindow_getHeight(native_window);
int curr_format = ANativeWindow_getFormat(native_window);
int buff_w = IJKALIGN(overlay->w, 2);
int buff_h = IJKALIGN(overlay->h, 2);
// 获取当时像素buffer所对应的像素格局
AndroidHalFourccDescriptor *overlayDesc = native_window_get_desc(overlay->format);
if (!overlayDesc) {
ALOGE("SDL_Android_NativeWindow_display_l: unknown overlay format: %d", overlay->format);
return -1;
}
// 获取当时ANativeWindow对应的像素格局
AndroidHalFourccDescriptor *voutDesc = native_window_get_desc(curr_format);
if (!voutDesc || voutDesc->hal_format != overlayDesc->hal_format) {
ALOGD("ANativeWindow_setBuffersGeometry: w=%d, h=%d, f=%.4s(0x%x) => w=%d, h=%d, f=%.4s(0x%x)",
curr_w, curr_h, (char*) &curr_format, curr_format,
buff_w, buff_h, (char*) &overlay->format, overlay->format);
//假如当时window的像素格局与装备的像素格局不同,进行装备,可能会失败
retval = ANativeWindow_setBuffersGeometry(native_window, buff_w, buff_h, overlayDesc->hal_format);
if (retval < 0) {
ALOGE("SDL_Android_NativeWindow_display_l: ANativeWindow_setBuffersGeometry: failed %d", retval);
return retval;
}
if (!voutDesc) {
ALOGE("SDL_Android_NativeWindow_display_l: unknown hal format %d", curr_format);
return -1;
}
}
ANativeWindow_Buffer out_buffer;
retval = ANativeWindow_lock(native_window, &out_buffer, NULL);
if (retval < 0) {
ALOGE("SDL_Android_NativeWindow_display_l: ANativeWindow_lock: failed %d", retval);
return retval;
}
//二次承认宽度是否设置成功
if (out_buffer.width != buff_w || out_buffer.height != buff_h) {
ALOGE("unexpected native window buffer (%p)(w:%d, h:%d, fmt:'%.4s'0x%x), expecting (w:%d, h:%d, fmt:'%.4s'0x%x)",
native_window,
out_buffer.width, out_buffer.height, (char*)&out_buffer.format, out_buffer.format,
buff_w, buff_h, (char*)&overlay->format, overlay->format);
// TODO: 8 set all black
ANativeWindow_unlockAndPost(native_window);
ANativeWindow_setBuffersGeometry(native_window, buff_w, buff_h, overlayDesc->hal_format);
return -1;
}
//进行像素数据复制,也即烘托
int render_ret = voutDesc->render(&out_buffer, overlay);
if (render_ret < 0) {
// TODO: 8 set all black
// return after unlock image;
}
retval = ANativeWindow_unlockAndPost(native_window);
if (retval < 0) {
ALOGE("SDL_Android_NativeWindow_display_l: ANativeWindow_unlockAndPost: failed %d", retval);
return retval;
}
return render_ret;
}
像素复制
目前咱们的overlay_format
是SDL_FCC_RV32
,依据装备其复制函数为android_render_on_rgb8888
。在装备中
static AndroidHalFourccDescriptor g_hal_fcc_map[] = {
// YV12
{ HAL_PIXEL_FORMAT_YV12, "HAL_YV12", HAL_PIXEL_FORMAT_YV12, android_render_on_yv12 },
{ SDL_FCC_YV12, "YV12", HAL_PIXEL_FORMAT_YV12, android_render_on_yv12 },
// RGB565
{ HAL_PIXEL_FORMAT_RGB_565, "HAL_RGB_565", HAL_PIXEL_FORMAT_RGB_565, android_render_on_rgb565 },
{ SDL_FCC_RV16, "RV16", HAL_PIXEL_FORMAT_RGB_565, android_render_on_rgb565 },
// RGB8888
{ HAL_PIXEL_FORMAT_RGBX_8888, "HAL_RGBX_8888", HAL_PIXEL_FORMAT_RGBX_8888, android_render_on_rgb8888 },
{ HAL_PIXEL_FORMAT_RGBA_8888, "HAL_RGBA_8888", HAL_PIXEL_FORMAT_RGBA_8888, android_render_on_rgb8888 },
{ HAL_PIXEL_FORMAT_BGRA_8888, "HAL_BGRA_8888", HAL_PIXEL_FORMAT_BGRA_8888, android_render_on_rgb8888 },
{ SDL_FCC_RV32, "RV32", HAL_PIXEL_FORMAT_RGBX_8888, android_render_on_rgb8888 },
};
AndroidHalFourccDescriptor *native_window_get_desc(int fourcc_or_hal)
{
for (int i = 0; i < NELEM(g_hal_fcc_map); ++i) {
AndroidHalFourccDescriptor *desc = &g_hal_fcc_map[i];
if (desc->fcc_or_hal == fourcc_or_hal)
return desc;
}
return NULL;
}
static int android_render_on_rgb8888(ANativeWindow_Buffer *out_buffer, const SDL_VoutOverlay *overlay)
{
assert(out_buffer);
assert(overlay);
switch (overlay->format) {
case SDL_FCC_RV32: {
return android_render_rgb32_on_rgb8888(out_buffer, overlay);
}
}
return -1;
}
static int android_render_rgb32_on_rgb8888(ANativeWindow_Buffer *out_buffer, const SDL_VoutOverlay *overlay)
{
return android_render_rgb_on_rgb(out_buffer, overlay, 32);
}
static int android_render_rgb_on_rgb(ANativeWindow_Buffer *out_buffer, const SDL_VoutOverlay *overlay, int bpp)
{
// SDLTRACE("SDL_VoutAndroid: android_render_rgb_on_rgb(%p)", overlay);
assert(overlay->format == SDL_FCC_RV16);
assert(overlay->planes == 1);
//stride=width+对齐
int min_height = IJKMIN(out_buffer->height, overlay->h);
int dst_stride = out_buffer->stride;
int src_line_size = overlay->pitches[0];
int dst_line_size = dst_stride * bpp / 8;//一行的字节数
uint8_t *dst_pixels = out_buffer->bits;
// 像素数据
const uint8_t *src_pixels = overlay->pixels[0];
if (dst_line_size == src_line_size) {
int plane_size = src_line_size * min_height;
// ALOGE("android_render_rgb_on_rgb (pix-match) %p %p %d", dst_pixels, src_pixels, plane_size);
memcpy(dst_pixels, src_pixels, plane_size);
} else {
// TODO: 9 padding
int bytewidth = IJKMIN(dst_line_size, src_line_size);
// ALOGE("android_render_rgb_on_rgb (pix-mismatch) %p %d %p %d %d %d", dst_pixels, dst_line_size, src_pixels, src_line_size, bytewidth, min_height);
av_image_copy_plane(dst_pixels, dst_line_size, src_pixels, src_line_size, bytewidth, min_height);
}
return 0;
}
至此,ijk在android平台上默许的视频解码和烘托流程都代码现已盯梢结束,下面来整体回忆一下简要流程:
1.调用decoder_decode_frame
获取软解码视频帧AVFrame
,里边包括了像素数据
2.将AVFrame
包装为SDL_VoutOverlay
,按方针格局进行像素转化,SDL_VoutOverlay
中pixels
将指向可烘托的像素数据,并加入到行列中。
3.烘托线程不断的从行列中获取出SDL_VoutOverlay
4.依据像素格局进行烘托,即从SDL_VoutOverlay
所指向的像素copy到ANativeWindow
中
以上是个人见解,假如过错或不同见解,欢迎指正和沟通。