blob: 2fecb44dece8782b8ee3e8c73d453cfd5bc408a9 [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "host-common/MediaH264DecoderVideoToolBox.h"
#include "host-common/H264NaluParser.h"
#include <VideoToolbox/VideoToolbox.h>
#include <cstdint>
#include <string>
#include <vector>
#include <stdio.h>
#include <string.h>
#ifndef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder
#define kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder CFSTR("RequireHardwareAcceleratedVideoDecoder")
#endif
#define MEDIA_H264_DEBUG 0
#if MEDIA_H264_DEBUG
#define H264_DPRINT(fmt,...) fprintf(stderr, "h264-videotoolbox-dec: %s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
#else
#define H264_DPRINT(fmt,...)
#endif
namespace android {
namespace emulation {
using InitContextParam = H264PingInfoParser::InitContextParam;
using DecodeFrameParam = H264PingInfoParser::DecodeFrameParam;
using ResetParam = H264PingInfoParser::ResetParam;
using GetImageParam = H264PingInfoParser::GetImageParam;
using H264NaluType = H264NaluParser::H264NaluType;
MediaH264DecoderVideoToolBox::MediaH264DecoderVideoToolBox(
uint64_t id,
H264PingInfoParser parser)
: mId(id), mParser(parser) {
H264_DPRINT("created MediaH264DecoderVideoToolBox %p", this);
}
MediaH264DecoderPlugin* MediaH264DecoderVideoToolBox::clone() {
return new MediaH264DecoderVideoToolBox(mId, mParser);
}
MediaH264DecoderVideoToolBox::~MediaH264DecoderVideoToolBox() {
destroyH264Context();
}
// static
void MediaH264DecoderVideoToolBox::videoToolboxDecompressCallback(void* opaque,
void* sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags flags,
CVImageBufferRef image_buffer,
CMTime pts,
CMTime duration) {
H264_DPRINT("%s", __func__);
auto ptr = static_cast<MediaH264DecoderVideoToolBox*>(opaque);
if (ptr->mDecodedFrame) {
CVPixelBufferRelease(ptr->mDecodedFrame);
ptr->mDecodedFrame = nullptr;
}
if (!image_buffer) {
H264_DPRINT("%s: output image buffer is null", __func__);
return;
}
ptr->mOutputPts = pts.value;
ptr->mDecodedFrame = CVPixelBufferRetain(image_buffer);
// Image is ready to be comsumed
ptr->copyFrame();
ptr->mImageReady = true;
H264_DPRINT("Got decoded frame");
}
// static
CFDictionaryRef MediaH264DecoderVideoToolBox::createOutputBufferAttributes(int width,
int height,
OSType pix_fmt) {
CFMutableDictionaryRef buffer_attributes;
CFMutableDictionaryRef io_surface_properties;
CFNumberRef cv_pix_fmt;
CFNumberRef w;
CFNumberRef h;
w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width);
h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height);
cv_pix_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt);
buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault,
4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
io_surface_properties = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (pix_fmt) {
CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, cv_pix_fmt);
}
CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, io_surface_properties);
CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w);
CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h);
// Not sure if this will work becuase we are passing the pixel buffer back into the guest
CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfaceOpenGLTextureCompatibilityKey, kCFBooleanTrue);
CFRelease(io_surface_properties);
CFRelease(cv_pix_fmt);
CFRelease(w);
CFRelease(h);
return buffer_attributes;
}
// static
CMSampleBufferRef MediaH264DecoderVideoToolBox::createSampleBuffer(CMFormatDescriptionRef fmtDesc,
void* buffer,
size_t sz) {
OSStatus status;
CMBlockBufferRef blockBuf = nullptr;
CMSampleBufferRef sampleBuf = nullptr;
status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, // structureAllocator
buffer, // memoryBlock
sz, // blockLength
kCFAllocatorNull, // blockAllocator
NULL, // customBlockSource
0, // offsetToData
sz, // dataLength
0, // flags
&blockBuf);
if (!status) {
status = CMSampleBufferCreate(kCFAllocatorDefault, // allocator
blockBuf, // dataBuffer
TRUE, // dataReady
0, // makeDataReadyCallback
0, // makeDataReadyRefCon
fmtDesc, // formatDescription
1, // numSamples
0, // numSampleTimingEntries
NULL, // sampleTimingArray
0, // numSampleSizeEntries
NULL, // sampleSizeArray
&sampleBuf);
}
if (blockBuf) {
CFRelease(blockBuf);
}
return sampleBuf;
}
// static
OSType MediaH264DecoderVideoToolBox::toNativePixelFormat(PixelFormat pixFmt) {
switch (pixFmt) {
case PixelFormat::YUV420P:
return kCVPixelFormatType_420YpCbCr8Planar;
case PixelFormat::UYVY422:
return kCVPixelFormatType_422YpCbCr8;
case PixelFormat::BGRA8888:
return kCVPixelFormatType_32BGRA;
default:
H264_DPRINT("Unsupported VideoToolbox pixel format");
return '0000';
}
}
// static
void* MediaH264DecoderVideoToolBox::getReturnAddress(void* ptr) {
uint8_t* xptr = (uint8_t*)ptr;
void* pint = (void*)(xptr + 256);
return pint;
}
void MediaH264DecoderVideoToolBox::createCMFormatDescription() {
uint8_t* parameterSets[2] = {mSPS.data(), mPPS.data()};
size_t parameterSetSizes[2] = {mSPS.size(), mPPS.size()};
if (mCmFmtDesc) {
CFRelease(mCmFmtDesc);
mCmFmtDesc = nullptr;
}
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
kCFAllocatorDefault,
2,
(const uint8_t *const*)parameterSets,
parameterSetSizes,
4,
&mCmFmtDesc);
if (status == noErr) {
H264_DPRINT("Created CMFormatDescription from SPS/PPS sets");
} else {
H264_DPRINT("Unable to create CMFormatDescription (%d)\n", (int)status);
}
}
CFDataRef MediaH264DecoderVideoToolBox::createVTDecoderConfig() {
CFDataRef data = nullptr;
return data;
}
void MediaH264DecoderVideoToolBox::initH264Context(void* ptr) {
InitContextParam param{};
mParser.parseInitContextParams(ptr, param);
initH264ContextInternal(param.width, param.height, param.outputWidth,
param.outputHeight, param.outputPixelFormat);
}
void MediaH264DecoderVideoToolBox::initH264ContextInternal(
unsigned int width,
unsigned int height,
unsigned int outWidth,
unsigned int outHeight,
PixelFormat outPixFmt) {
H264_DPRINT("%s(w=%u h=%u out_w=%u out_h=%u pixfmt=%u)",
__func__, width, height, outWidth, outHeight, (uint8_t)outPixFmt);
mWidth = width;
mHeight = height;
mOutputWidth = outWidth;
mOutputHeight = outHeight;
mOutPixFmt = outPixFmt;
mOutBufferSize = outWidth * outHeight * 3 / 2;
}
void MediaH264DecoderVideoToolBox::reset(void* ptr) {
destroyH264Context();
ResetParam param{};
mParser.parseResetParams(ptr, param);
initH264ContextInternal(param.width, param.height, param.outputWidth,
param.outputHeight, param.outputPixelFormat);
}
void MediaH264DecoderVideoToolBox::destroyH264Context() {
H264_DPRINT("%s", __func__);
if (mDecoderSession) {
VTDecompressionSessionInvalidate(mDecoderSession);
CFRelease(mDecoderSession);
mDecoderSession = nullptr;
}
if (mCmFmtDesc) {
CFRelease(mCmFmtDesc);
mCmFmtDesc = nullptr;
}
if (mDecodedFrame) {
CVPixelBufferRelease(mDecodedFrame);
mDecodedFrame = nullptr;
}
}
static void dumpBytes(const uint8_t* img, size_t szBytes, bool all = false) {
#if MEDIA_H264_DEBUG
printf("data=");
size_t numBytes = szBytes;
if (!all) {
numBytes = 32;
}
for (size_t i = 0; i < (numBytes > szBytes ? szBytes : numBytes); ++i) {
if (i % 8 == 0) {
printf("\n");
}
printf("0x%02x ", img[i]);
}
printf("\n");
#endif
}
void MediaH264DecoderVideoToolBox::decodeFrame(void* ptr) {
DecodeFrameParam param{};
mParser.parseDecodeFrameParams(ptr, param);
const uint8_t* frame = param.pData;
size_t szBytes = param.size;
uint64_t pts = param.pts;
decodeFrameInternal(param.pConsumedBytes, param.pDecoderErrorCode, frame, szBytes, pts, 0);
}
void MediaH264DecoderVideoToolBox::oneShotDecode(std::vector<uint8_t> & data, uint64_t pts) {
//TODO: check if data has more than one Nalu
decodeFrameInternal(nullptr, nullptr, data.data(), data.size(), pts, 0);
}
void MediaH264DecoderVideoToolBox::decodeFrameInternal(size_t* pRetSzBytes, int32_t* pRetErr, const uint8_t* frame, size_t szBytes, uint64_t pts, size_t consumedSzBytes) {
// IMPORTANT: There is an assumption that each |frame| we get from the guest are contain
// complete NALUs. Usually, an H.264 bitstream would be continuously fed to us, but in this case,
// it seems that the Android frameworks passes us complete NALUs, and may be more than one NALU at
// a time.
//
// Let's only process one NALU per decodeFrame() call because, as soon as we receive a PPS NALU,
// we need to let the OMX plugin know to reset it's state because we are also recreating our
// decoder context.
H264_DPRINT("%s(frame=%p, sz=%zu)", __func__, frame, szBytes);
Err h264Err = Err::NoErr;
const uint8_t* currentNalu = H264NaluParser::getNextStartCodeHeader(frame, szBytes);
if (currentNalu == nullptr) {
H264_DPRINT("No start code header found in this frame");
h264Err = Err::NoDecodedFrame;
// TODO: return the error code and num bytes processed, szBytes.
if (pRetSzBytes) *pRetSzBytes = szBytes;
if (pRetErr) *pRetErr = (int32_t)h264Err;
return;
}
const uint8_t* nextNalu = nullptr;
size_t remaining = szBytes - (currentNalu - frame);
// Figure out the size of |currentNalu|.
size_t currentNaluSize = remaining;
// 3 is the minimum size of the start code header (3 or 4 bytes).
dumpBytes(currentNalu, currentNaluSize);
nextNalu = H264NaluParser::getNextStartCodeHeader(currentNalu + 3, remaining - 3);
if (nextNalu != nullptr) {
currentNaluSize = nextNalu - currentNalu;
}
// |data| is currentNalu, but with the start code header discarded.
uint8_t* data = nullptr;
H264NaluType naluType = H264NaluParser::getFrameNaluType(currentNalu, currentNaluSize, &data);
size_t dataSize = currentNaluSize - (data - currentNalu);
const std::string naluTypeStr = H264NaluParser::naluTypeToString(naluType);
H264_DPRINT("Got frame type=%u (%s)", (uint8_t)naluType, naluTypeStr.c_str());
// We can't do anything until we set up a CMFormatDescription from a set of SPS and PPS NALUs.
// So just discard the NALU.
if (naluType != H264NaluType::SPS && naluType != H264NaluType::PPS &&
mCmFmtDesc == nullptr) {
H264_DPRINT("CMFormatDescription not set up yet. Need SPS/PPS frames.");
h264Err = Err::NALUIgnored;
if (pRetSzBytes) *pRetSzBytes = currentNaluSize;
if (pRetErr) *pRetErr = (int32_t)h264Err;
return;
}
switch (naluType) {
case H264NaluType::SPS:
// We should be getting a PPS frame on the next decodeFrame(). Once we have
// both sps and pps, we can create/recreate the decoder session.
// Don't include the start code header when we copy the sps/pps.
mSPS.assign(data, data + dataSize);
if (!mIsLoadingFromSnapshot) {
mSnapshotState = SnapshotState{};
std::vector<uint8_t> vec;
vec.assign(currentNalu, currentNalu + currentNaluSize);
mSnapshotState.saveSps(vec);
}
break;
case H264NaluType::PPS:
mPPS.assign(data, data + dataSize);
createCMFormatDescription();
// TODO: We will need to recreate the decompression session whenever we get a
// resolution change.
if (mDecoderSession != nullptr) {
H264_DPRINT("Decoder session is restarting");
//h264Err = Err::DecoderRestarted;
}
if (!mIsLoadingFromSnapshot) {
std::vector<uint8_t> vec;
vec.assign(currentNalu, currentNalu + currentNaluSize);
mSnapshotState.savePps(vec);
mSnapshotState.savedPackets.clear();
}
recreateDecompressionSession();
break;
case H264NaluType::SEI:
// dumpBytes(nextNalu, remaining, true);
// In some cases, after the SPS and PPS NALUs are emitted, we'll get a frame that
// contains both an SEI NALU and a CodedSliceIDR NALU.
handleSEIFrame(currentNalu, currentNaluSize);
break;
case H264NaluType::CodedSliceIDR:
handleIDRFrame(currentNalu, currentNaluSize, pts);
if (!mIsLoadingFromSnapshot) {
H264_DPRINT("disacarding previously saved frames %d", (int)mSnapshotState.savedPackets.size());
mSnapshotState.savedPackets.clear();
mSnapshotState.savePacket(currentNalu, currentNaluSize, pts);
}
break;
case H264NaluType::CodedSliceNonIDR:
handleNonIDRFrame(currentNalu, currentNaluSize, pts);
if (!mIsLoadingFromSnapshot) {
mSnapshotState.savePacket(currentNalu, currentNaluSize, pts);
}
break;
default:
H264_DPRINT("Support for nalu_type=%u not implemented", (uint8_t)naluType);
break;
}
remaining -= currentNaluSize;
currentNalu = nextNalu;
// return two things: the error code and the number of bytes we processed.
if (pRetSzBytes) *pRetSzBytes = currentNaluSize + consumedSzBytes;
if (pRetErr) *pRetErr = (int32_t)h264Err;
// disable recursive decoding due to the possibility of session creation failure
// keep it simple for now
//if (currentNalu) {
// decodeFrameInternal(ptr, currentNalu, remaining, pts, consumedSzBytes + currentNaluSize);
//}
}
void MediaH264DecoderVideoToolBox::handleIDRFrame(const uint8_t* ptr, size_t szBytes, uint64_t pts) {
H264_DPRINT("Got IDR frame (sz=%zu)", szBytes);
uint8_t* fptr = const_cast<uint8_t*>(ptr);
// We can assume fptr has a valid start code header because it has already
// gone through validation in H264NaluParser.
uint8_t startHeaderSz = fptr[2] == 1 ? 3 : 4;
uint32_t dataSz = szBytes - startHeaderSz;
std::unique_ptr<uint8_t> idr(new uint8_t[dataSz + 4]);
uint32_t dataSzNl = htonl(dataSz);
// AVCC format requires us to replace the start code header on this NALU
// with the size of the data. Start code is either 0x000001 or 0x00000001.
// The size needs to be the first four bytes in network byte order.
memcpy(idr.get(), &dataSzNl, 4);
memcpy(idr.get() + 4, ptr + startHeaderSz, dataSz);
CMSampleBufferRef sampleBuf = nullptr;
sampleBuf = createSampleBuffer(mCmFmtDesc, (void*)idr.get(), dataSz + 4);
if (!sampleBuf) {
H264_DPRINT("%s: Failed to create CMSampleBufferRef", __func__);
return;
}
CMSampleBufferSetOutputPresentationTimeStamp(sampleBuf, CMTimeMake(pts, 1));
OSStatus status;
status = VTDecompressionSessionDecodeFrame(mDecoderSession,
sampleBuf,
0, // decodeFlags
NULL, // sourceFrameRefCon
0); // infoFlagsOut
if (status == noErr) {
// TODO: this call blocks until the frame has been decoded. Perhaps it will be
// more efficient to signal the guest when the frame is ready to be read instead.
status = VTDecompressionSessionWaitForAsynchronousFrames(mDecoderSession);
mIsInFlush = false;
} else {
H264_DPRINT("%s: Failed to decompress frame (err=%d)", __func__, status);
}
CFRelease(sampleBuf);
H264_DPRINT("Success decoding IDR frame");
}
void MediaH264DecoderVideoToolBox::handleNonIDRFrame(const uint8_t* ptr, size_t szBytes, uint64_t pts) {
// Same as handling an IDR frame
handleIDRFrame(ptr, szBytes, pts);
}
void MediaH264DecoderVideoToolBox::handleSEIFrame(const uint8_t* ptr, size_t szBytes) {
H264_DPRINT("NOT IMPLEMENTED");
}
void MediaH264DecoderVideoToolBox::flush(void* ptr) {
H264_DPRINT("%s: NOT IMPLEMENTED", __func__);
mIsInFlush = true;
}
void MediaH264DecoderVideoToolBox::copyFrame() {
if (mIsLoadingFromSnapshot) return;
int imgWidth = CVPixelBufferGetWidth(mDecodedFrame);
int imgHeight = CVPixelBufferGetHeight(mDecodedFrame);
int imageSize = CVPixelBufferGetDataSize(mDecodedFrame);
int stride = CVPixelBufferGetBytesPerRow(mDecodedFrame);
mOutputWidth = imgWidth;
mOutputHeight = imgHeight;
mOutBufferSize = mOutputWidth * mOutputHeight * 3 / 2;
H264_DPRINT("copying size=%d dimension=[%dx%d] stride=%d", imageSize, imgWidth, imgHeight, stride);
// Copies the image data to the guest.
mSavedDecodedFrame.resize(imgWidth * imgHeight * 3 / 2);
uint8_t* dst = mSavedDecodedFrame.data();
CVPixelBufferLockBaseAddress(mDecodedFrame, kCVPixelBufferLock_ReadOnly);
if (CVPixelBufferIsPlanar(mDecodedFrame)) {
imageSize = 0; // add up the size from the planes
int planes = CVPixelBufferGetPlaneCount(mDecodedFrame);
for (int i = 0; i < planes; ++i) {
void* planeData = CVPixelBufferGetBaseAddressOfPlane(mDecodedFrame, i);
int linesize = CVPixelBufferGetBytesPerRowOfPlane(mDecodedFrame, i);
int planeWidth = CVPixelBufferGetWidthOfPlane(mDecodedFrame, i);
int planeHeight = CVPixelBufferGetHeightOfPlane(mDecodedFrame, i);
H264_DPRINT("plane=%d data=%p linesize=%d pwidth=%d pheight=%d", i, planeData, linesize, planeWidth, planeHeight);
// For kCVPixelFormatType_420YpCbCr8Planar, plane 0 is Y, UV planes are 1 and 2
if (planeWidth != imgWidth && planeWidth != imgWidth / 2) {
H264_DPRINT("ERROR: Unable to determine YUV420 plane type");
continue;
}
// Sometimes the buffer stride can be longer than the actual data width. This means that
// the extra bytes are just padding and need to be discarded.
if (linesize <= planeWidth) {
int sz = planeHeight * planeWidth;
imageSize += sz;
memcpy(dst, planeData, sz);
dst += sz;
} else {
// Need to copy line by line
int sz = planeWidth;
for (int j = 0; j < planeHeight; ++j) {
uint8_t* ptr = (uint8_t*)planeData;
ptr += linesize * j;
memcpy(dst, ptr, sz);
imageSize += sz;
dst += sz;
}
}
}
if (imageSize != mOutBufferSize) {
H264_DPRINT("ERROR: Total size of planes not same as guestSz (guestSz=%u, imageSize=%d)", mOutBufferSize, imageSize);
}
} else {
if (imageSize > mOutBufferSize) {
H264_DPRINT("Buffer size mismatch (guestSz=%u, imageSize=%d). Using guestSz instead.", mOutBufferSize, imageSize);
imageSize = mOutBufferSize;
}
// IMPORTANT: mDecodedFrame must be locked before accessing the contents with CPU
void* data = CVPixelBufferGetBaseAddress(mDecodedFrame);
memcpy(dst, data, imageSize);
}
CVPixelBufferUnlockBaseAddress(mDecodedFrame, kCVPixelBufferLock_ReadOnly);
}
void MediaH264DecoderVideoToolBox::getImage(void* ptr) {
// return parameters:
// 1) either size of the image (> 0) or error code (<= 0).
// 2) image width
// 3) image height
GetImageParam param{};
mParser.parseGetImageParams(ptr, param);
int* retErr = param.pDecoderErrorCode;
uint32_t* retWidth = param.pRetWidth;
uint32_t* retHeight = param.pRetHeight;
uint64_t* retPts = param.pRetPts;
uint32_t* retColorPrimaries = param.pRetColorPrimaries;
uint32_t* retColorRange = param.pRetColorRange;
uint32_t* retColorTransfer = param.pRetColorTransfer;
uint32_t* retColorSpace = param.pRetColorSpace;
if (!mDecodedFrame) {
H264_DPRINT("%s: frame is null", __func__);
*retErr = static_cast<int>(Err::NoDecodedFrame);
return;
}
if (!mImageReady) {
bool hasMoreFrames = false;
if (mIsInFlush) {
OSStatus status = noErr;
status = VTDecompressionSessionWaitForAsynchronousFrames(mDecoderSession);
if (status == noErr) {
hasMoreFrames = mImageReady;
if (hasMoreFrames) {
H264_DPRINT("%s: got frame in flush mode", __func__);
}
}
}
if (!hasMoreFrames) {
H264_DPRINT("%s: no new frame yet", __func__);
*retErr = static_cast<int>(Err::NoDecodedFrame);
return;
}
}
*retWidth = mOutputWidth;
*retHeight = mOutputHeight;
if (mParser.version() == 200 && param.hostColorBufferId >= 0) {
mRenderer.renderToHostColorBuffer(param.hostColorBufferId,
mOutputWidth, mOutputHeight,
mSavedDecodedFrame.data());
} else {
memcpy(param.pDecodedFrame, mSavedDecodedFrame.data(), mSavedDecodedFrame.size());;
}
*retErr = mSavedDecodedFrame.size();
*retPts = mOutputPts;
H264_DPRINT("Copying completed pts %lld", (long long)mOutputPts);
mImageReady = false;
}
void MediaH264DecoderVideoToolBox::recreateDecompressionSession() {
if (mCmFmtDesc == nullptr) {
H264_DPRINT("CMFormatDescription not created. Need sps and pps NALUs.");
return;
}
// Create a new VideoToolbox decoder session if one already exists
if (mDecoderSession != nullptr) {
// TODO: Once we implement async frame readback, we'll need to flush all of the frames here and
// store them somewhere for the guest to read later.
VTDecompressionSessionInvalidate(mDecoderSession);
CFRelease(mDecoderSession);
mDecoderSession = nullptr;
if (mDecodedFrame) {
CVPixelBufferRelease(mDecodedFrame);
mDecodedFrame = nullptr;
}
}
CMVideoCodecType codecType = kCMVideoCodecType_H264;
CFMutableDictionaryRef decoder_spec = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(decoder_spec,
kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder,
kCFBooleanTrue);
CFDictionaryRef bufAttr = createOutputBufferAttributes(mOutputWidth,
mOutputHeight,
toNativePixelFormat(mOutPixFmt));
VTDecompressionOutputCallbackRecord decoderCb;
decoderCb.decompressionOutputCallback = videoToolboxDecompressCallback;
decoderCb.decompressionOutputRefCon = this;
OSStatus status;
status = VTDecompressionSessionCreate(NULL, // allocator
mCmFmtDesc, // videoFormatDescription
decoder_spec, // videoDecoderSpecification
bufAttr, // destinationImageBufferAttributes
&decoderCb, // outputCallback
&mDecoderSession); // decompressionSessionOut
if (decoder_spec) {
CFRelease(decoder_spec);
}
if (bufAttr) {
CFRelease(bufAttr);
}
mIsInFlush = false;
mState = DecoderState::BAD_STATE;
switch (status) {
case kVTVideoDecoderNotAvailableNowErr:
H264_DPRINT("VideoToolbox session not available");
return;
case kVTVideoDecoderUnsupportedDataFormatErr:
H264_DPRINT("VideoToolbox does not support this format");
return;
case kVTVideoDecoderMalfunctionErr:
H264_DPRINT("VideoToolbox malfunction");
return;
case kVTVideoDecoderBadDataErr:
H264_DPRINT("VideoToolbox reported invalid data");
return;
case 0:
H264_DPRINT("VideoToolbox session created");
mState = DecoderState::GOOD_STATE;
return;
default:
H264_DPRINT("Unknown VideoToolbox session creation error %d", status);
return;
}
}
void MediaH264DecoderVideoToolBox::save(base::Stream* stream) const {
stream->putBe32(mParser.version());
stream->putBe32(mWidth);
stream->putBe32(mHeight);
stream->putBe32((int)mOutPixFmt);
const int hasContext = mDecoderSession != nullptr ? 1 : 0;
stream->putBe32(hasContext);
if (mImageReady) {
mSnapshotState.saveDecodedFrame(
mSavedDecodedFrame, mOutputWidth, mOutputHeight,
ColorAspects{}, mOutputPts);
} else {
mSnapshotState.savedDecodedFrame.data.clear();
}
H264_DPRINT("saving packets now %d",
(int)(mSnapshotState.savedPackets.size()));
mSnapshotState.save(stream);
}
bool MediaH264DecoderVideoToolBox::load(base::Stream* stream) {
mIsLoadingFromSnapshot = true;
uint32_t version = stream->getBe32();
mParser = H264PingInfoParser{version};
mWidth = stream->getBe32();
mHeight = stream->getBe32();
mOutPixFmt = (PixelFormat)stream->getBe32();
const int hasContext = stream->getBe32();
if (hasContext) {
initH264ContextInternal(mWidth, mHeight, mWidth, mHeight, mOutPixFmt);
}
mSnapshotState.load(stream);
if (hasContext && mSnapshotState.sps.size() > 0) {
oneShotDecode(mSnapshotState.sps, 0);
if (mSnapshotState.pps.size() > 0) {
oneShotDecode(mSnapshotState.pps, 0);
if (mSnapshotState.savedPackets.size() > 0) {
for (int i = 0; i < mSnapshotState.savedPackets.size(); ++i) {
PacketInfo& pkt = mSnapshotState.savedPackets[i];
oneShotDecode(pkt.data, pkt.pts);
}
}
}
}
if (mSnapshotState.savedDecodedFrame.data.size() > 0) {
mSavedDecodedFrame = std::move(mSnapshotState.savedDecodedFrame.data);
mOutBufferSize = mSnapshotState.savedDecodedFrame.data.size();
mOutputWidth = mSnapshotState.savedDecodedFrame.width;
mOutputHeight = mSnapshotState.savedDecodedFrame.height;
mOutputPts = mSnapshotState.savedDecodedFrame.pts;
mImageReady = true;
} else {
mOutputWidth = mWidth;
mOutputHeight = mHeight;
mOutBufferSize = mOutputWidth * mOutputHeight * 3 / 2;
mImageReady = false;
}
mIsLoadingFromSnapshot = false;
return true;
}
} // namespace emulation
} // namespace android