mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-14 14:20:45 +01:00

Bug: webrtc:12338 Change-Id: Ifaad29ccb63b0f2f3aeefb77dae061ebc7f87e6c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227024 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34561}
327 lines
11 KiB
C++
327 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*
|
|
*/
|
|
|
|
#include "sdk/objc/components/video_codec/nalu_rewriter.h"
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
|
|
using H264::kAud;
|
|
using H264::kSps;
|
|
using H264::NaluIndex;
|
|
using H264::NaluType;
|
|
using H264::ParseNaluType;
|
|
|
|
const char kAnnexBHeaderBytes[4] = {0, 0, 0, 1};
|
|
const size_t kAvccHeaderByteSize = sizeof(uint32_t);
|
|
|
|
bool H264CMSampleBufferToAnnexBBuffer(CMSampleBufferRef avcc_sample_buffer,
|
|
bool is_keyframe,
|
|
rtc::Buffer* annexb_buffer) {
|
|
RTC_DCHECK(avcc_sample_buffer);
|
|
|
|
// Get format description from the sample buffer.
|
|
CMVideoFormatDescriptionRef description =
|
|
CMSampleBufferGetFormatDescription(avcc_sample_buffer);
|
|
if (description == nullptr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get sample buffer's description.";
|
|
return false;
|
|
}
|
|
|
|
// Get parameter set information.
|
|
int nalu_header_size = 0;
|
|
size_t param_set_count = 0;
|
|
OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
|
|
description, 0, nullptr, nullptr, ¶m_set_count, &nalu_header_size);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get parameter set.";
|
|
return false;
|
|
}
|
|
RTC_CHECK_EQ(nalu_header_size, kAvccHeaderByteSize);
|
|
RTC_DCHECK_EQ(param_set_count, 2);
|
|
|
|
// Truncate any previous data in the buffer without changing its capacity.
|
|
annexb_buffer->SetSize(0);
|
|
|
|
// Place all parameter sets at the front of buffer.
|
|
if (is_keyframe) {
|
|
size_t param_set_size = 0;
|
|
const uint8_t* param_set = nullptr;
|
|
for (size_t i = 0; i < param_set_count; ++i) {
|
|
status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
|
|
description, i, ¶m_set, ¶m_set_size, nullptr, nullptr);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get parameter set.";
|
|
return false;
|
|
}
|
|
// Update buffer.
|
|
annexb_buffer->AppendData(kAnnexBHeaderBytes, sizeof(kAnnexBHeaderBytes));
|
|
annexb_buffer->AppendData(reinterpret_cast<const char*>(param_set),
|
|
param_set_size);
|
|
}
|
|
}
|
|
|
|
// Get block buffer from the sample buffer.
|
|
CMBlockBufferRef block_buffer =
|
|
CMSampleBufferGetDataBuffer(avcc_sample_buffer);
|
|
if (block_buffer == nullptr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get sample buffer's block buffer.";
|
|
return false;
|
|
}
|
|
CMBlockBufferRef contiguous_buffer = nullptr;
|
|
// Make sure block buffer is contiguous.
|
|
if (!CMBlockBufferIsRangeContiguous(block_buffer, 0, 0)) {
|
|
status = CMBlockBufferCreateContiguous(
|
|
nullptr, block_buffer, nullptr, nullptr, 0, 0, 0, &contiguous_buffer);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to flatten non-contiguous block buffer: "
|
|
<< status;
|
|
return false;
|
|
}
|
|
} else {
|
|
contiguous_buffer = block_buffer;
|
|
// Retain to make cleanup easier.
|
|
CFRetain(contiguous_buffer);
|
|
block_buffer = nullptr;
|
|
}
|
|
|
|
// Now copy the actual data.
|
|
char* data_ptr = nullptr;
|
|
size_t block_buffer_size = CMBlockBufferGetDataLength(contiguous_buffer);
|
|
status = CMBlockBufferGetDataPointer(contiguous_buffer, 0, nullptr, nullptr,
|
|
&data_ptr);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get block buffer data.";
|
|
CFRelease(contiguous_buffer);
|
|
return false;
|
|
}
|
|
size_t bytes_remaining = block_buffer_size;
|
|
while (bytes_remaining > 0) {
|
|
// The size type here must match `nalu_header_size`, we expect 4 bytes.
|
|
// Read the length of the next packet of data. Must convert from big endian
|
|
// to host endian.
|
|
RTC_DCHECK_GE(bytes_remaining, (size_t)nalu_header_size);
|
|
uint32_t* uint32_data_ptr = reinterpret_cast<uint32_t*>(data_ptr);
|
|
uint32_t packet_size = CFSwapInt32BigToHost(*uint32_data_ptr);
|
|
// Update buffer.
|
|
annexb_buffer->AppendData(kAnnexBHeaderBytes, sizeof(kAnnexBHeaderBytes));
|
|
annexb_buffer->AppendData(data_ptr + nalu_header_size, packet_size);
|
|
|
|
size_t bytes_written = packet_size + sizeof(kAnnexBHeaderBytes);
|
|
bytes_remaining -= bytes_written;
|
|
data_ptr += bytes_written;
|
|
}
|
|
RTC_DCHECK_EQ(bytes_remaining, (size_t)0);
|
|
|
|
CFRelease(contiguous_buffer);
|
|
return true;
|
|
}
|
|
|
|
bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
|
|
size_t annexb_buffer_size,
|
|
CMVideoFormatDescriptionRef video_format,
|
|
CMSampleBufferRef* out_sample_buffer,
|
|
CMMemoryPoolRef memory_pool) {
|
|
RTC_DCHECK(annexb_buffer);
|
|
RTC_DCHECK(out_sample_buffer);
|
|
RTC_DCHECK(video_format);
|
|
*out_sample_buffer = nullptr;
|
|
|
|
AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
|
|
if (reader.SeekToNextNaluOfType(kSps)) {
|
|
// Buffer contains an SPS NALU - skip it and the following PPS
|
|
const uint8_t* data;
|
|
size_t data_len;
|
|
if (!reader.ReadNalu(&data, &data_len)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read SPS";
|
|
return false;
|
|
}
|
|
if (!reader.ReadNalu(&data, &data_len)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read PPS";
|
|
return false;
|
|
}
|
|
} else {
|
|
// No SPS NALU - start reading from the first NALU in the buffer
|
|
reader.SeekToStart();
|
|
}
|
|
|
|
// Allocate memory as a block buffer.
|
|
CMBlockBufferRef block_buffer = nullptr;
|
|
CFAllocatorRef block_allocator = CMMemoryPoolGetAllocator(memory_pool);
|
|
OSStatus status = CMBlockBufferCreateWithMemoryBlock(
|
|
kCFAllocatorDefault, nullptr, reader.BytesRemaining(), block_allocator,
|
|
nullptr, 0, reader.BytesRemaining(), kCMBlockBufferAssureMemoryNowFlag,
|
|
&block_buffer);
|
|
if (status != kCMBlockBufferNoErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to create block buffer.";
|
|
return false;
|
|
}
|
|
|
|
// Make sure block buffer is contiguous.
|
|
CMBlockBufferRef contiguous_buffer = nullptr;
|
|
if (!CMBlockBufferIsRangeContiguous(block_buffer, 0, 0)) {
|
|
status = CMBlockBufferCreateContiguous(kCFAllocatorDefault, block_buffer,
|
|
block_allocator, nullptr, 0, 0, 0,
|
|
&contiguous_buffer);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to flatten non-contiguous block buffer: "
|
|
<< status;
|
|
CFRelease(block_buffer);
|
|
return false;
|
|
}
|
|
} else {
|
|
contiguous_buffer = block_buffer;
|
|
block_buffer = nullptr;
|
|
}
|
|
|
|
// Get a raw pointer into allocated memory.
|
|
size_t block_buffer_size = 0;
|
|
char* data_ptr = nullptr;
|
|
status = CMBlockBufferGetDataPointer(contiguous_buffer, 0, nullptr,
|
|
&block_buffer_size, &data_ptr);
|
|
if (status != kCMBlockBufferNoErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get block buffer data pointer.";
|
|
CFRelease(contiguous_buffer);
|
|
return false;
|
|
}
|
|
RTC_DCHECK(block_buffer_size == reader.BytesRemaining());
|
|
|
|
// Write Avcc NALUs into block buffer memory.
|
|
AvccBufferWriter writer(reinterpret_cast<uint8_t*>(data_ptr),
|
|
block_buffer_size);
|
|
while (reader.BytesRemaining() > 0) {
|
|
const uint8_t* nalu_data_ptr = nullptr;
|
|
size_t nalu_data_size = 0;
|
|
if (reader.ReadNalu(&nalu_data_ptr, &nalu_data_size)) {
|
|
writer.WriteNalu(nalu_data_ptr, nalu_data_size);
|
|
}
|
|
}
|
|
|
|
// Create sample buffer.
|
|
status = CMSampleBufferCreate(kCFAllocatorDefault, contiguous_buffer, true,
|
|
nullptr, nullptr, video_format, 1, 0, nullptr,
|
|
0, nullptr, out_sample_buffer);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to create sample buffer.";
|
|
CFRelease(contiguous_buffer);
|
|
return false;
|
|
}
|
|
CFRelease(contiguous_buffer);
|
|
return true;
|
|
}
|
|
|
|
CMVideoFormatDescriptionRef CreateVideoFormatDescription(
|
|
const uint8_t* annexb_buffer,
|
|
size_t annexb_buffer_size) {
|
|
const uint8_t* param_set_ptrs[2] = {};
|
|
size_t param_set_sizes[2] = {};
|
|
AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
|
|
// Skip everyting before the SPS, then read the SPS and PPS
|
|
if (!reader.SeekToNextNaluOfType(kSps)) {
|
|
return nullptr;
|
|
}
|
|
if (!reader.ReadNalu(¶m_set_ptrs[0], ¶m_set_sizes[0])) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read SPS";
|
|
return nullptr;
|
|
}
|
|
if (!reader.ReadNalu(¶m_set_ptrs[1], ¶m_set_sizes[1])) {
|
|
RTC_LOG(LS_ERROR) << "Failed to read PPS";
|
|
return nullptr;
|
|
}
|
|
|
|
// Parse the SPS and PPS into a CMVideoFormatDescription.
|
|
CMVideoFormatDescriptionRef description = nullptr;
|
|
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
|
|
kCFAllocatorDefault, 2, param_set_ptrs, param_set_sizes, 4, &description);
|
|
if (status != noErr) {
|
|
RTC_LOG(LS_ERROR) << "Failed to create video format description.";
|
|
return nullptr;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
AnnexBBufferReader::AnnexBBufferReader(const uint8_t* annexb_buffer,
|
|
size_t length)
|
|
: start_(annexb_buffer), length_(length) {
|
|
RTC_DCHECK(annexb_buffer);
|
|
offsets_ = H264::FindNaluIndices(annexb_buffer, length);
|
|
offset_ = offsets_.begin();
|
|
}
|
|
|
|
AnnexBBufferReader::~AnnexBBufferReader() = default;
|
|
|
|
bool AnnexBBufferReader::ReadNalu(const uint8_t** out_nalu,
|
|
size_t* out_length) {
|
|
RTC_DCHECK(out_nalu);
|
|
RTC_DCHECK(out_length);
|
|
*out_nalu = nullptr;
|
|
*out_length = 0;
|
|
|
|
if (offset_ == offsets_.end()) {
|
|
return false;
|
|
}
|
|
*out_nalu = start_ + offset_->payload_start_offset;
|
|
*out_length = offset_->payload_size;
|
|
++offset_;
|
|
return true;
|
|
}
|
|
|
|
size_t AnnexBBufferReader::BytesRemaining() const {
|
|
if (offset_ == offsets_.end()) {
|
|
return 0;
|
|
}
|
|
return length_ - offset_->start_offset;
|
|
}
|
|
|
|
void AnnexBBufferReader::SeekToStart() {
|
|
offset_ = offsets_.begin();
|
|
}
|
|
|
|
bool AnnexBBufferReader::SeekToNextNaluOfType(NaluType type) {
|
|
for (; offset_ != offsets_.end(); ++offset_) {
|
|
if (offset_->payload_size < 1)
|
|
continue;
|
|
if (ParseNaluType(*(start_ + offset_->payload_start_offset)) == type)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
AvccBufferWriter::AvccBufferWriter(uint8_t* const avcc_buffer, size_t length)
|
|
: start_(avcc_buffer), offset_(0), length_(length) {
|
|
RTC_DCHECK(avcc_buffer);
|
|
}
|
|
|
|
bool AvccBufferWriter::WriteNalu(const uint8_t* data, size_t data_size) {
|
|
// Check if we can write this length of data.
|
|
if (data_size + kAvccHeaderByteSize > BytesRemaining()) {
|
|
return false;
|
|
}
|
|
// Write length header, which needs to be big endian.
|
|
uint32_t big_endian_length = CFSwapInt32HostToBig(data_size);
|
|
memcpy(start_ + offset_, &big_endian_length, sizeof(big_endian_length));
|
|
offset_ += sizeof(big_endian_length);
|
|
// Write data.
|
|
memcpy(start_ + offset_, data, data_size);
|
|
offset_ += data_size;
|
|
return true;
|
|
}
|
|
|
|
size_t AvccBufferWriter::BytesRemaining() const {
|
|
return length_ - offset_;
|
|
}
|
|
|
|
} // namespace webrtc
|