// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/base/mac/videotoolbox_helpers.h" #include #include #include "base/big_endian.h" #include "base/memory/scoped_ptr.h" namespace media { namespace video_toolbox { base::ScopedCFTypeRef DictionaryWithKeysAndValues(CFTypeRef* keys, CFTypeRef* values, size_t size) { return base::ScopedCFTypeRef(CFDictionaryCreate( kCFAllocatorDefault, keys, values, size, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); } base::ScopedCFTypeRef DictionaryWithKeyValue(CFTypeRef key, CFTypeRef value) { CFTypeRef keys[1] = {key}; CFTypeRef values[1] = {value}; return DictionaryWithKeysAndValues(keys, values, 1); } base::ScopedCFTypeRef ArrayWithIntegers(const int* v, size_t size) { std::vector numbers; numbers.reserve(size); for (const int* end = v + size; v < end; ++v) numbers.push_back(CFNumberCreate(nullptr, kCFNumberSInt32Type, v)); base::ScopedCFTypeRef array(CFArrayCreate( kCFAllocatorDefault, reinterpret_cast(&numbers[0]), numbers.size(), &kCFTypeArrayCallBacks)); for (auto& number : numbers) { CFRelease(number); } return array; } base::ScopedCFTypeRef ArrayWithIntegerAndFloat(int int_val, float float_val) { std::array numbers = { {CFNumberCreate(nullptr, kCFNumberSInt32Type, &int_val), CFNumberCreate(nullptr, kCFNumberFloat32Type, &float_val)}}; base::ScopedCFTypeRef array(CFArrayCreate( kCFAllocatorDefault, reinterpret_cast(numbers.data()), numbers.size(), &kCFTypeArrayCallBacks)); for (auto& number : numbers) CFRelease(number); return array; } // Wrapper class for writing AnnexBBuffer output into. class AnnexBBuffer { public: virtual bool Reserve(size_t size) = 0; virtual void Append(const char* s, size_t n) = 0; virtual size_t GetReservedSize() const = 0; }; class RawAnnexBBuffer : public AnnexBBuffer { public: RawAnnexBBuffer(char* annexb_buffer, size_t annexb_buffer_size) : annexb_buffer_(annexb_buffer), annexb_buffer_size_(annexb_buffer_size), annexb_buffer_offset_(0) {} bool Reserve(size_t size) override { reserved_size_ = size; return size <= annexb_buffer_size_; } void Append(const char* s, size_t n) override { memcpy(annexb_buffer_ + annexb_buffer_offset_, s, n); annexb_buffer_offset_ += n; DCHECK_GE(reserved_size_, annexb_buffer_offset_); } size_t GetReservedSize() const override { return reserved_size_; } private: char* annexb_buffer_; size_t annexb_buffer_size_; size_t annexb_buffer_offset_; size_t reserved_size_; DISALLOW_IMPLICIT_CONSTRUCTORS(RawAnnexBBuffer); }; class StringAnnexBBuffer : public AnnexBBuffer { public: explicit StringAnnexBBuffer(std::string* str_annexb_buffer) : str_annexb_buffer_(str_annexb_buffer) {} bool Reserve(size_t size) override { str_annexb_buffer_->reserve(size); return true; } void Append(const char* s, size_t n) override { str_annexb_buffer_->append(s, n); } size_t GetReservedSize() const override { return str_annexb_buffer_->size(); } private: std::string* str_annexb_buffer_; DISALLOW_IMPLICIT_CONSTRUCTORS(StringAnnexBBuffer); }; template void CopyNalsToAnnexB(char* avcc_buffer, const size_t avcc_size, AnnexBBuffer* annexb_buffer) { static_assert(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 || sizeof(NalSizeType) == 4, "NAL size type has unsupported size"); static const char startcode_3[3] = {0, 0, 1}; DCHECK(avcc_buffer); DCHECK(annexb_buffer); size_t bytes_left = avcc_size; while (bytes_left > 0) { DCHECK_GT(bytes_left, sizeof(NalSizeType)); NalSizeType nal_size; base::ReadBigEndian(avcc_buffer, &nal_size); bytes_left -= sizeof(NalSizeType); avcc_buffer += sizeof(NalSizeType); DCHECK_GE(bytes_left, nal_size); annexb_buffer->Append(startcode_3, sizeof(startcode_3)); annexb_buffer->Append(avcc_buffer, nal_size); bytes_left -= nal_size; avcc_buffer += nal_size; } } bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, AnnexBBuffer* annexb_buffer, bool keyframe) { // Perform two pass, one to figure out the total output size, and another to // copy the data after having performed a single output allocation. Note that // we'll allocate a bit more because we'll count 4 bytes instead of 3 for // video NALs. OSStatus status; // Get the sample buffer's block buffer and format description. auto bb = CoreMediaGlue::CMSampleBufferGetDataBuffer(sbuf); DCHECK(bb); auto fdesc = CoreMediaGlue::CMSampleBufferGetFormatDescription(sbuf); DCHECK(fdesc); size_t bb_size = CoreMediaGlue::CMBlockBufferGetDataLength(bb); size_t total_bytes = bb_size; size_t pset_count; int nal_size_field_bytes; status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes); if (status == CoreMediaGlue::kCMFormatDescriptionBridgeError_InvalidParameter) { DLOG(WARNING) << " assuming 2 parameter sets and 4 bytes NAL length header"; pset_count = 2; nal_size_field_bytes = 4; } else if (status != noErr) { DLOG(ERROR) << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " << status; return false; } if (keyframe) { const uint8_t* pset; size_t pset_size; for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); if (status != noErr) { DLOG(ERROR) << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " << status; return false; } total_bytes += pset_size + nal_size_field_bytes; } } if (!annexb_buffer->Reserve(total_bytes)) { DLOG(ERROR) << "Cannot fit encode output into bitstream buffer. Requested:" << total_bytes; return false; } // Copy all parameter sets before keyframes. if (keyframe) { const uint8_t* pset; size_t pset_size; for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); if (status != noErr) { DLOG(ERROR) << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " << status; return false; } static const char startcode_4[4] = {0, 0, 0, 1}; annexb_buffer->Append(startcode_4, sizeof(startcode_4)); annexb_buffer->Append(reinterpret_cast(pset), pset_size); } } // Block buffers can be composed of non-contiguous chunks. For the sake of // keeping this code simple, flatten non-contiguous block buffers. base::ScopedCFTypeRef contiguous_bb( bb, base::scoped_policy::RETAIN); if (!CoreMediaGlue::CMBlockBufferIsRangeContiguous(bb, 0, 0)) { contiguous_bb.reset(); status = CoreMediaGlue::CMBlockBufferCreateContiguous( kCFAllocatorDefault, bb, kCFAllocatorDefault, nullptr, 0, 0, 0, contiguous_bb.InitializeInto()); if (status != noErr) { DLOG(ERROR) << " CMBlockBufferCreateContiguous failed: " << status; return false; } } // Copy all the NAL units. In the process convert them from AVCC format // (length header) to AnnexB format (start code). char* bb_data; status = CoreMediaGlue::CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr, nullptr, &bb_data); if (status != noErr) { DLOG(ERROR) << " CMBlockBufferGetDataPointer failed: " << status; return false; } if (nal_size_field_bytes == 1) { CopyNalsToAnnexB(bb_data, bb_size, annexb_buffer); } else if (nal_size_field_bytes == 2) { CopyNalsToAnnexB(bb_data, bb_size, annexb_buffer); } else if (nal_size_field_bytes == 4) { CopyNalsToAnnexB(bb_data, bb_size, annexb_buffer); } else { NOTREACHED(); } return true; } bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, bool keyframe, std::string* annexb_buffer) { StringAnnexBBuffer buffer(annexb_buffer); return CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); } bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, bool keyframe, size_t annexb_buffer_size, char* annexb_buffer, size_t* used_buffer_size) { RawAnnexBBuffer buffer(annexb_buffer, annexb_buffer_size); const bool copy_rv = CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); *used_buffer_size = buffer.GetReservedSize(); return copy_rv; } SessionPropertySetter::SessionPropertySetter( base::ScopedCFTypeRef session, const VideoToolboxGlue* const glue) : session_(session), glue_(glue) {} SessionPropertySetter::~SessionPropertySetter() {} bool SessionPropertySetter::Set(CFStringRef key, int32_t value) { DCHECK(session_); DCHECK(glue_); base::ScopedCFTypeRef cfvalue( CFNumberCreate(nullptr, kCFNumberSInt32Type, &value)); return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; } bool SessionPropertySetter::Set(CFStringRef key, bool value) { DCHECK(session_); DCHECK(glue_); CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse; return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; } bool SessionPropertySetter::Set(CFStringRef key, CFStringRef value) { DCHECK(session_); DCHECK(glue_); return glue_->VTSessionSetProperty(session_, key, value) == noErr; } bool SessionPropertySetter::Set(CFStringRef key, CFArrayRef value) { DCHECK(session_); DCHECK(glue_); return glue_->VTSessionSetProperty(session_, key, value) == noErr; } } // namespace video_toolbox } // namespace media