MTK TV平台 ImagePlayer介绍

1,069 阅读6分钟

ImagePlayer为mtk实现的用来播放图片的播放器, 接入MediaPlayer中, 能实现4k播放

/vendor/mediatek/proprietary_tv/open/common/libraries/media/mmplayer/imgplayer

ImagePlayer接入

  1. 添加player_type枚举, IMG_PLAYER 为9

image

  1. 在MediaPlayerFactory registerBuiltinFactories中, new ImagePlayerFactory注册到sFactoryMap中

image

  1. 继承MediaPlayerFactory::IFactory, 实现ImagePlayerFactory, 重写需要source类型的scoreFactory及createPlayer方法. 这里可以看到ImagePlayerFactory 只重写了source类型为fd或者url类型的方法. 意味着只有这两个类型的播放,才可能选到ImagePlayer

    class ImagePlayerFactory : public MediaPlayerFactory::IFactory { public: virtual float scoreFactory(const sp& /client/, const char* url, const sp &httpService, float curScore) { static const float kOurScore = 0.9;

        if (kOurScore <= curScore)
            return 0.0;
    
        const char *ptr = strrchr(url, '/');
        if (ptr != NULL) {
            ptr += 1;
            if ((strcasecmp(ptr, "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz") == 0)
                || (strcasecmp(ptr, "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz") == 0)
                || (strcasecmp(ptr, "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz") == 0)
                || (strcasecmp(ptr, "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz") == 0)
                || (strcasecmp(ptr, "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz") == 0)) {
                return 0.0;
            }
        }
    

    #ifdef SUPPORT_IMAGEPLAYER_DLOPEN if (ImagePlayerDrive::canBeUsed(url, httpService)) { #else if (ImagePlayer::canBeUsed(url, httpService)) { #endif return 1.0; }

        return 0.0;
    }
    
    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               int fd,
                               int64_t /*offset*/,
                               int64_t /*length*/,
                               float curScore) {
        static const float kOurScore = 0.9;
    
        if (kOurScore <= curScore)
            return 0.0;
    

    #ifdef SUPPORT_IMAGEPLAYER_DLOPEN if (ImagePlayerDrive::canBeUsed(fd)) { #else if (ImagePlayer::canBeUsed(fd)) { #endif return 1.0; }

        return 0.0;
    }
    
    virtual sp<MediaPlayerBase> createPlayer(pid_t /* pid */) {
    

    #ifdef SUPPORT_IMAGEPLAYER_DLOPEN return new ImagePlayerDrive(); #else return new ImagePlayer(); #endif } }; #endif

ImagePlayer实现

主要类继承调用关系如下:

ImagePlayer 使用流程:

  1. 创建MediaPlayer对象mMediaPlayer = new MediaPlayer()

  2. 设置播放文件 mMediaPlayer.setDataSource

  3. 通过mMediaPlayer.setDisplay()设置显示surface

  4. mMediaPlayer.Prepare

  5. mMediaPlayer.setParameter 设置图片的size和orientation等

  6. 开始播放mMediaPlayer.start

  7. 播放结束mMediaPlayer.stop

所有的接口调用,最终都走到ImagePlayer

ImagePlayer 创建流程

setDataSource流程

setVideoSurfaceTexture流程

Prepare 流程

setParameter流程

在app设置对应surface参数下来后, 就启动了解码流程, 并生成mDstImageDrawable

start流程

app调用start后, 直接调用show显示mDstImageDrawable

displayByMvop最终调用ANativeWindow方法将buffer 写到surface中


bool ImagePlayerImpl::displayByMvop(sp<ImageDrawable> &imageDrawable)
{
    ALOGV("%s Enter", __FUNCTION__);

    bool ret = false;
    ANativeWindowBuffer* anb = NULL;
    status_t err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &anb);

    if (err != OK) {
        ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err);
    } else {
#if defined(AN_O_BEFORE)
        sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
#else
        sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
#endif

        void* img = NULL;
#if defined(BUILD_PURE_AOSP) || !defined(AN_O_BEFORE)
        err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_PRIVATE_2, (void**)(&img));
#else
        err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
#endif
        if (err != NO_ERROR) {
            ALOGE("error pushing image frame: lock failed: %s (%d)",
                    strerror(-err), -err);
            goto error;
        }

        bool bGen4MvopSuccess;
        bGen4MvopSuccess = imageDrawable->draw4Mvop(img);
        if (bGen4MvopSuccess != true) {
            err = UNKNOWN_ERROR;
            ALOGE("error generate image frame");
            goto error;
        }

        err = buf->unlock();
        if (err != NO_ERROR) {
            ALOGE("error pushing image frame: unlock failed: %s (%d)",
                    strerror(-err), -err);
            goto error;
        }

        ALOGD("[%s] queueBuffer to surface", __FUNCTION__);
        err = mNativeWindow->queueBuffer(mNativeWindow.get(),
                buf->getNativeBuffer(), -1);
        if (err != NO_ERROR) {
            ALOGE("error pushing image frame: queueBuffer failed: %s (%d)",
                    strerror(-err), -err);
            goto error;
        }

        ret = true;

        error:
        anb = NULL;
    }

    return ret;
}

stop流程类似,不详细介绍了

SkImageDrawable与HwImageDrawable

通过设置PROPERTY_IMGHWDEC_SW_SCALE属性, 可以决定使用SkImageDrawable 还是使用HwImageDrawable

#define PROPERTY_IMGHWDEC_SW_SCALE "vendor.mediatek.imghwdec.swscale"

image

SkImageDrawable的scale, rotate, crop等都使用软件做, 新建一个SkCanvas, 软件处理好的数据, 再画到SkCanvas上


sp<SkBitmapHolder> SkImageDrawable::doRotate(const sp<SkBitmapHolder> &srcBitmapHolder, float degrees)
{
    const SkBitmap *srcBitmap = srcBitmapHolder->getSkData();
    if (srcBitmap == NULL)
        return NULL;

    SkBitmap *devBitmap = new SkBitmap();
    SkMatrix *matrix = new SkMatrix();
    SkCanvas *canvas = NULL;

    int sourceWidth = srcBitmap->width();
    int sourceHeight = srcBitmap->height();
    double radian = SkDegreesToRadians(degrees);

    int dstWidth = sourceWidth * fabs(cos(radian)) + sourceHeight * fabs(sin(radian));
    int dstHeight = sourceHeight * fabs(cos(radian)) + sourceWidth * fabs(sin(radian));
    ...
    devBitmap->setInfo(SkImageInfo::Make(dstWidth, dstHeight, kN32_SkColorType, kOpaque_SkAlphaType));

    devBitmap->allocPixels();
    devBitmap->eraseARGB(255, 0, 0, 0);

    canvas = new SkCanvas(*devBitmap);

    matrix->postRotate(degrees, sourceWidth / 2, sourceHeight / 2);
    matrix->postTranslate((dstWidth - sourceWidth) / 2, (dstHeight - sourceHeight) / 2);
    
    canvas->concat(*matrix);
    canvas->drawBitmap(*srcBitmap, 0, 0);


    delete canvas;
    delete matrix;

    return new SkBitmapHolder(devBitmap);
}

HwImageDrawable的scale, rotate, crop等都使用硬件做, 在HwImageDrawable中只进行相关参数的计算

bool HwImageDrawable::rotate(float degrees)
{
    ALOGV("%s degrees: %f", __FUNCTION__, degrees);
    if (rotate2integer(degrees) % 90 != 0) {
        ALOGE("%s bad degrees: %d(%f)", __FUNCTION__, rotate2integer(degrees), degrees);
        return false;
    }
    mDrawParameters = doRotate(mDrawParameters, degrees);
    dumpDrawParameters(__FUNCTION__, mDrawParameters);
    return true;
}

HwImageDrawable::DrawParameters HwImageDrawable::doRotate(const HwImageDrawable::DrawParameters &parameters, float degrees)
{
    HwImageDrawable::DrawParameters dstDrawParameters = parameters;
    dstDrawParameters.dstDrawMatrix = rotateMatrix(parameters.dstSurfaceSize, parameters.dstDrawMatrix, degrees);
    dstDrawParameters.dstSurfaceSize = rotateSize(parameters.dstSurfaceSize, degrees);
    dstDrawParameters.degrees = (float)rotate2integer(parameters.degrees + degrees);
    return dstDrawParameters;
}

在draw4Mvop方法中,将buffer及参数传到mImgDispClient中进行相关scale, rotate等操作

bool HwImageDrawable::draw4Mvop(void *buffer)
{
    bool ret = false;
    sp<ImgDispClient> imgDispClient = mImgDispClient.promote();

    if (imgDispClient != NULL) {
        dumpDrawParameters(__FUNCTION__, mDrawParameters);
        ImgDispClient::ConvertParameters convertParameters;
        convertParameters.source = mDrawableBuffer->getBufferInfo();
        convertParameters.crop.x = mDrawParameters.srcCropMatrix.x;
        convertParameters.crop.y = mDrawParameters.srcCropMatrix.y;
        convertParameters.crop.width = mDrawParameters.srcCropMatrix.width;
        convertParameters.crop.height = mDrawParameters.srcCropMatrix.height;
        convertParameters.angle = transRotateDegrees(mDrawParameters.degrees);
        // only set width and height for destination
        convertParameters.destination.width = mDrawParameters.dstSurfaceSize.width;
        convertParameters.destination.height = mDrawParameters.dstSurfaceSize.height;
        convertParameters.draw.x = mDrawParameters.dstDrawMatrix.x;
        convertParameters.draw.y = mDrawParameters.dstDrawMatrix.y;
        convertParameters.draw.width = mDrawParameters.dstDrawMatrix.width;
        convertParameters.draw.height = mDrawParameters.dstDrawMatrix.height;
        ret = imgDispClient->convertMvopHeader(buffer, convertParameters);
    } else {
        ALOGW("mImgDispClient promote failed");
    }

    return ret;
}

convertMvopHeader 最终调用到ImageDisp的ImageDisp_convert方法


//vendor/mediatek/proprietary_tv/open/common/libraries/media/mmplayer/imgplayer/ImageDisp.cpp
bool ImageDisp_convert(void *img, ImgDisp_ConvertParameters_t *pConvertParameters)
{
    ...
    bRet = PortingLayer_DISP_Convert_Format(pConvertParameters, mOutputColorFmt);
    ...
    return imageDisp_genDispFrameFormat(
                dff,
                pConvertParameters->destination.width,
                alignedWidth,
                pConvertParameters->destination.height,
                u64PhyPaddr,
                (unsigned char *)pPhyVaddr,
                pConvertParameters->destination.width,
                pConvertParameters->destination.height);
}

//vendor/mediatek/proprietary_tv/open/common/libraries/media/mmplayer/imgplayer/porting_layer/mi3/ImageDISP_Portinglayer_mi3.cpp
static inline bool OSD_Convert_Format_ex(const OSD_ConvertParameters parameters)
{
 ...
    MI_OSD_BeginDraw();

    do {
 ...
            eRet = MI_OSD_FillRect(hDstSurface, &stRectAttr, &stFillRectBlitOpt, NULL /*&stWaitRenderJobId*/);
            if (eRet != MI_OK) {
                ALOGE("MI_OSD_FillRect failed, eRet:%d", eRet);
                ret = false;
                break;
            }
        }
 ...
        eRet = MI_OSD_Bitblit(hSrcSurface, &stSrcRect, hDstSurface, &stDstRect, &stBlitOpt, &stWaitRenderJobId);
        if (eRet != MI_OK) {
            ALOGE("MI_OSD_Bitblit failed, eRet:%d", eRet);
            ret = false;
            break;
        }
        // Wait for bitblt operation done.
        eRet = MI_OSD_WaitRenderDone(&stWaitRenderJobId);
        if (eRet != MI_OK) {
            ALOGE("MI_OSD_WaitRenderDone failed, eRet:%d", eRet);
            ret = false;
            break;
        }
    } while (0);
...
    MI_OSD_EndDraw();

    return ret;
}

SwImageDecoder与HwImageDecoder

通过设置PROPERTY_IMGHWDEC_SW_DEC属性,可以决定使用SwImageDecoder还是HwImageDecoder

#define PROPERTY_IMGHWDEC_SW_DEC "vendor.mediatek.imghwdec.swdec"

SwImageDecoder与HwImageDecoder的区别主要在于是硬件解码,还是软件解码

SwImageDecoder 主要用Skia库进行解码,使用MediaMetadataRetriever来获取图片帧


SkBitmap* SwImageDecoder::decode(SkCodecType *codec, int sampleSize, SkBaseStream *stream)
{
...
    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType, alphaType);

    SkBitmap *bitmap = new SkBitmap();
...
    SkAndroidCodec::AndroidOptions codecOptions;

    if (sampleSize > 0) {
       codecOptions.fSampleSize = sampleSize;
    }

    SkCodec::Result result = codec->getAndroidPixels(decodeInfo, bitmap->getPixels(), bitmap->rowBytes(), &codecOptions);
 ...
}

HwImageDecoder使用硬件解码


sp<ImageDrawable> HwImageDecoder::onDecode(int sampleSize, sp<ImageStreamFactory> &streamFactory, const wp<ImgDispClient> &imgDispClient)
{
    sp<ImageDrawable> imageDrawable = NULL;

    do {
       ...
        ALOGV("MS_HIDL_Image_HwDec_GetReadBuffer start");
        if (false == MS_HIDL_Image_HwDec_GetReadBuffer(&stReadBufferInfo)) {
            ALOGE("MS_HIDL_Image_HwDec_GetReadBuffer fail, go SW");
            mForceSw = true;
            break;
        }

        ...
        if (haveRead > 0 && mIsCancelDecode == false) {
          ...
            if (false == MS_HIDL_Image_HwDec_Open(&stOpenInfo)) {
                ALOGE("%s %d, MS_HIDL_Image_HwDec_Open fail go SW", __FUNCTION__, __LINE__);
                mForceSw = true;
                break;
            }
            ALOGD("%s open hw decoder done", __FUNCTION__);
            if (false == MS_HIDL_Image_HwDec_Decode()) {
                ALOGE("%s %d, MS_HIDL_Image_HwDec_Decode fail go SW", __FUNCTION__, __LINE__);
                mForceSw = true;
                MS_HIDL_Image_HwDec_Close();
                break;
            }
        }

        ImageHwDec_Status_e eDecStatus = ImageHwDec_STATUS_DECODING;
        while (!mIsCancelDecode && eDecStatus == ImageHwDec_STATUS_DECODING) {
            ImageHwDec_PhotoInfo_t stPhotoInfo;
            eDecStatus = MS_HIDL_Image_HwDec_GetDecodeStatus();
            if (eDecStatus == ImageHwDec_STATUS_DECODE_OK) {
                ALOGD("%s hw decode done", __FUNCTION__);

                bool bGetInfo = MS_HIDL_Image_HwDec_GetPhotoInfo(&stPhotoInfo);
 ...
}

经过HIDL调用后, 最终调用到Mi_Image接口

int32_t ImgHwDecWrapper::ImgHwDecWrapperDecode()
{
    Status status;
    if (!mInitDone) {
        ALOGE("%s %d not init done", __FUNCTION__, __LINE__);
        return -1;
    }

    status = mImgHwDec->ImageHwDec_decode();
    if (status != Status::OK) {
        ALOGE("%s %d ImageHwDec_decode failed, status: %d",  __FUNCTION__, __LINE__, static_cast<int32_t>(status));
        return -1;
    }
    return 0;
}

bool ImageHwDec_decode()
{
...
    eRet = MI_IMGDEC_Decode(mImgdecHandle, stIMGInfo);
    if (eRet != MI_OK) {
        ALOGE("MI_IMGDEC_Decode failed\n");
        return false;
    }
...
}

解码流程, 以HEIF格式为例

HIEF格式介绍

HEIF/heic图片文件解析

ImagePlayer方式:

ImagePlayer -> SwImageDecoder -> SkCodec -> SkHeifCodec -> HeifDecoderImpl --> MediaMetadataRetriever::getImageRectAtIndex -> FrameDecoder::extractFrame

SwImageDecoder创建流程

std::unique_ptr<SkCodec> SkCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
                                                 Result* outResult, SkPngChunkReader* chunkReader) {
   ...
        for (DecoderProc proc : gDecoderProcs) {
            if (proc.IsFormat(buffer, bytesRead)) {
                return proc.MakeFromStream(std::move(stream), outResult);
            }
        }

...
}

static constexpr DecoderProc gDecoderProcs[] = {
#ifdef SK_HAS_JPEG_LIBRARY
    { SkJpegCodec::IsJpeg, SkJpegCodec::MakeFromStream },
#endif
#ifdef SK_HAS_WEBP_LIBRARY
    { SkWebpCodec::IsWebp, SkWebpCodec::MakeFromStream },
#endif
    { SkGifCodec::IsGif, SkGifCodec::MakeFromStream },
#ifdef SK_HAS_PNG_LIBRARY
    { SkIcoCodec::IsIco, SkIcoCodec::MakeFromStream },
#endif
    { SkBmpCodec::IsBmp, SkBmpCodec::MakeFromStream },
    { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream },
#ifdef SK_HAS_HEIF_LIBRARY
    { SkHeifCodec::IsHeif, SkHeifCodec::MakeFromStream },
#endif
};

image

image


status_t FrameDecoder::init(
        int64_t frameTimeUs, size_t numFrames, int option, int colorFormat) {
 ...
    sp<MediaCodec> decoder = MediaCodec::CreateByComponentName(
            looper, mComponentName, &err);
    if (decoder.get() == NULL || err != OK) {
        ALOGW("Failed to instantiate decoder [%s]", mComponentName.c_str());
        return (decoder.get() == NULL) ? NO_MEMORY : err;
    }
...
    err = decoder->configure(
            videoFormat, NULL /* surface */, NULL /* crypto */, 0 /* flags */);
    if (err != OK) {
        ALOGW("configure returned error %d (%s)", err, asString(err));
        decoder->release();
        return err;
    }

    err = decoder->start();
    if (err != OK) {
        ALOGW("start returned error %d (%s)", err, asString(err));
        decoder->release();
        return err;
    }

    err = mSource->start();
    if (err != OK) {
        ALOGW("source failed to start: %d (%s)", err, asString(err));
        decoder->release();
        return err;
    }
    mDecoder = decoder;

    return OK;
}

status_t FrameDecoder::extractInternal() {
    status_t err = OK;
    bool done = false;
    size_t retriesLeft = kRetryCount;
    do {
        size_t index;
        int64_t ptsUs = 0ll;
        uint32_t flags = 0;

        // Queue as many inputs as we possibly can, then block on dequeuing
        // outputs. After getting each output, come back and queue the inputs
        // again to keep the decoder busy.
        while (mHaveMoreInputs) {
            err = mDecoder->dequeueInputBuffer(&index, 0);
            if (err != OK) {
                ALOGV("Timed out waiting for input");
                if (retriesLeft) {
                    err = OK;
                }
                break;
            }
            sp<MediaCodecBuffer> codecBuffer;
            err = mDecoder->getInputBuffer(index, &codecBuffer);
            if (err != OK) {
                ALOGE("failed to get input buffer %zu", index);
                break;
            }

            MediaBufferBase *mediaBuffer = NULL;

            err = mSource->read(&mediaBuffer, &mReadOptions);
            mReadOptions.clearSeekTo();
            if (err != OK) {
                mHaveMoreInputs = false;
                if (!mFirstSample && err == ERROR_END_OF_STREAM) {
                    (void)mDecoder->queueInputBuffer(
                            index, 0, 0, 0, MediaCodec::BUFFER_FLAG_EOS);
                    err = OK;
                } else {
                    ALOGW("Input Error: err=%d", err);
                }
                break;
            }
      ...
        }

        while (err == OK) {
            size_t offset, size;
            // wait for a decoded buffer
            err = mDecoder->dequeueOutputBuffer(
                    &index,
                    &offset,
                    &size,
                    &ptsUs,
                    &flags,
                    kBufferTimeOutUs);

            if (err == INFO_FORMAT_CHANGED) {
                ALOGV("Received format change");
                err = mDecoder->getOutputFormat(&mOutputFormat);
            } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
                ALOGV("Output buffers changed");
                err = OK;
            } else {
                if (err == -EAGAIN /* INFO_TRY_AGAIN_LATER */ && --retriesLeft > 0) {
                    ALOGV("Timed-out waiting for output.. retries left = %zu", retriesLeft);
                    err = OK;
                } else if (err == OK) {
                    // If we're seeking with CLOSEST option and obtained a valid targetTimeUs
                    // from the extractor, decode to the specified frame. Otherwise we're done.
                    ALOGV("Received an output buffer, timeUs=%lld", (long long)ptsUs);
                    sp<MediaCodecBuffer> videoFrameBuffer;
                    err = mDecoder->getOutputBuffer(index, &videoFrameBuffer);
                    if (err != OK) {
                        ALOGE("failed to get output buffer %zu", index);
                        break;
                    }
                    err = onOutputReceived(videoFrameBuffer, mOutputFormat, ptsUs, &done);
                    mDecoder->releaseOutputBuffer(index);
                } else {
                    ALOGW("Received error %d (%s) instead of output", err, asString(err));
                    done = true;
                }
                break;
            }
        }
    } while (err == OK && !done);

    if (err != OK) {
        ALOGE("failed to get video frame (err %d)", err);
    }

    return err;
}

image

ImageView解码流程

ImageView的解码流程与ImagePlayer的类似

ImageView -> ImageDecoder -> SkCodec -> SkHeifCodec -> HeifDecoder --> getImageRectAtIndex -> FrameDecoder::extractFrame

Skia图片解析流程与图片编码原理初探

Android 图形显示系统(十五) Android Q Skia的绘制系统