blob: 3c22213c536ce801f3fb99a206ea94419b79ed56 [file] [log] [blame]
#include <source/DirectRenderer_iOS.h>
#include <media/stagefright/avc_utils.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
namespace android {
DirectRenderer_iOS::DirectRenderer_iOS()
: mVideoFormatDescription(nullptr),
mSession(nullptr) {
}
DirectRenderer_iOS::~DirectRenderer_iOS() {
if (mVideoFormatDescription) {
CFRelease(mVideoFormatDescription);
mVideoFormatDescription = NULL;
}
if (mSession) {
VTDecompressionSessionInvalidate(mSession);
CFRelease(mSession);
mSession = NULL;
}
}
static void OnFrameReady(
void *decompressionOutputRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration) {
static_cast<DirectRenderer_iOS *>(
decompressionOutputRefCon)->render(imageBuffer);
}
void DirectRenderer_iOS::setFormat(size_t index, const sp<AMessage> &format) {
ALOGI("DirectRenderer_iOS::setFormat(%zu) => %s",
index,
format->debugString().c_str());
sp<ABuffer> csd0;
CHECK(format->findBuffer("csd-0", &csd0));
CHECK(csd0->size() >= 4 && !memcmp(csd0->data(), "\x00\x00\x00\x01", 4));
sp<ABuffer> csd1;
CHECK(format->findBuffer("csd-1", &csd1));
CHECK(csd1->size() >= 4 && !memcmp(csd1->data(), "\x00\x00\x00\x01", 4));
int32_t width, height;
CHECK(format->findInt32("width", &width));
CHECK(format->findInt32("height", &height));
const uint8_t *parameterSets[2] = {
csd0->data() + 4,
csd1->data() + 4,
};
const size_t parameterSetSizes[2] = {
csd0->size() - 4,
csd1->size() - 4,
};
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
kCFAllocatorDefault,
sizeof(parameterSets) / sizeof(parameterSets[0]),
parameterSets,
parameterSetSizes,
4 /* NALUnitHeaderLength */,
&mVideoFormatDescription);
CHECK_EQ(status, noErr);
CFDictionaryRef videoDecoderSpecification = NULL;
CFMutableDictionaryRef destinationImageBufferAttrs =
CFDictionaryCreateMutable(
kCFAllocatorDefault,
0 /* capacity */,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(
destinationImageBufferAttrs,
kCVPixelBufferOpenGLESCompatibilityKey,
kCFBooleanTrue);
SInt32 pixelType = kCVPixelFormatType_32BGRA;
CFNumberRef value =
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelType);
CFDictionarySetValue(
destinationImageBufferAttrs,
kCVPixelBufferPixelFormatTypeKey,
value);
CFRelease(value);
CFDictionaryRef surfaceProps =
CFDictionaryCreate(
kCFAllocatorDefault,
NULL /* keys */,
NULL /* values */,
0 /* numValues */,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(
destinationImageBufferAttrs,
kCVPixelBufferIOSurfacePropertiesKey,
surfaceProps);
CFRelease(surfaceProps);
surfaceProps = NULL;
VTDecompressionOutputCallbackRecord outputCallback = {
.decompressionOutputCallback = OnFrameReady,
.decompressionOutputRefCon = this,
};
status = VTDecompressionSessionCreate(
kCFAllocatorDefault,
mVideoFormatDescription,
videoDecoderSpecification,
destinationImageBufferAttrs,
&outputCallback,
&mSession);
CHECK_EQ(status, noErr);
}
static sp<ABuffer> ReplaceStartCodesWithLength(const sp<ABuffer> &buffer) {
sp<ABuffer> outBuf = new ABuffer(buffer->size() + 128); // Replacing 2 byte length with 4 byte startcodes takes its toll...
uint8_t *outData = outBuf->data();
size_t outOffset = 0;
const uint8_t *data = buffer->data();
size_t size = buffer->size();
const uint8_t *nalStart;
size_t nalSize;
while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
outData[outOffset++] = (nalSize >> 24) & 0xff;
outData[outOffset++] = (nalSize >> 16) & 0xff;
outData[outOffset++] = (nalSize >> 8) & 0xff;
outData[outOffset++] = nalSize & 0xff;
memcpy(&outData[outOffset], nalStart, nalSize);
outOffset += nalSize;
}
outBuf->setRange(0, outOffset);
return outBuf;
}
void DirectRenderer_iOS::queueAccessUnit(
size_t index, const sp<ABuffer> &accessUnit) {
sp<ABuffer> sampleBuf = ReplaceStartCodesWithLength(accessUnit);
CMBlockBufferRef blockBuffer;
OSStatus status = CMBlockBufferCreateWithMemoryBlock(
kCFAllocatorDefault,
sampleBuf->data(),
sampleBuf->size(),
kCFAllocatorNull,
NULL /* customBlockSource */,
0 /* offsetToData */,
sampleBuf->size(),
0 /* flags */,
&blockBuffer);
int64_t timeUs;
CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
const CMSampleTimingInfo timing = {
.duration = 0,
.presentationTimeStamp = CMTimeMake((timeUs * 9ll) / 100ll, 90000),
.decodeTimeStamp = kCMTimeInvalid,
};
const size_t size = sampleBuf->size();
CMSampleBufferRef sampleBuffer;
status = CMSampleBufferCreate(
kCFAllocatorDefault,
blockBuffer,
true /* dataReady */,
NULL /* makeDataReadyCallback */,
0 /* makeDataReadyRefCon */,
mVideoFormatDescription,
1 /* numSamples */,
1 /* numSampleTimingEntries */,
&timing /* sampleTimingArray */,
1 /* numSampleSizeEntries */,
&size /* sampleSizeArray */,
&sampleBuffer);
CFRelease(blockBuffer);
blockBuffer = NULL;
CHECK_EQ(status, noErr);
// NSLog(@"got a buffer of size %zu\n", sampleBuf->size());
VTDecodeInfoFlags infoFlags;
status = VTDecompressionSessionDecodeFrame(
mSession,
sampleBuffer,
kVTDecodeFrame_EnableAsynchronousDecompression
| kVTDecodeFrame_EnableTemporalProcessing,
0 /* sourceFrameRefCon */,
&infoFlags);
CFRelease(sampleBuffer);
sampleBuffer = NULL;
}
} // namespace android