Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_nalu.cc

Issue 2463313002: Add a webrtc{en,de}coderfactory implementation for VideoToolbox (Closed)
Patch Set: Code review comments Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 *
10 */
11
12 #include "webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_nalu.h"
13
14 #if defined(WEBRTC_VIDEO_TOOLBOX_SUPPORTED)
15
16 #include <CoreFoundation/CoreFoundation.h>
17 #include <memory>
18 #include <vector>
19
20 #include "webrtc/base/checks.h"
21 #include "webrtc/base/logging.h"
22 #include "webrtc/common_video/h264/h264_common.h"
23
24 namespace webrtc {
25
26 using H264::NaluType;
27 using H264::kAud;
28 using H264::kSps;
29 using H264::ParseNaluType;
30
31 const char kAnnexBHeaderBytes[4] = {0, 0, 0, 1};
32 const size_t kAvccHeaderByteSize = sizeof(uint32_t);
33
34 bool H264CMSampleBufferToAnnexBBuffer(
35 CMSampleBufferRef avcc_sample_buffer,
36 bool is_keyframe,
37 rtc::Buffer* annexb_buffer,
38 webrtc::RTPFragmentationHeader** out_header) {
39 RTC_DCHECK(avcc_sample_buffer);
40 RTC_DCHECK(out_header);
41 *out_header = nullptr;
42
43 // Get format description from the sample buffer.
44 CMVideoFormatDescriptionRef description =
45 CMSampleBufferGetFormatDescription(avcc_sample_buffer);
46 if (description == nullptr) {
47 LOG(LS_ERROR) << "Failed to get sample buffer's description.";
48 return false;
49 }
50
51 // Get parameter set information.
52 int nalu_header_size = 0;
53 size_t param_set_count = 0;
54 OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
55 description, 0, nullptr, nullptr, &param_set_count, &nalu_header_size);
56 if (status != noErr) {
57 LOG(LS_ERROR) << "Failed to get parameter set.";
58 return false;
59 }
60 // TODO(tkchin): handle other potential sizes.
61 RTC_DCHECK_EQ(nalu_header_size, 4);
62 RTC_DCHECK_EQ(param_set_count, 2u);
63
64 // Truncate any previous data in the buffer without changing its capacity.
65 annexb_buffer->SetSize(0);
66
67 size_t nalu_offset = 0;
68 std::vector<size_t> frag_offsets;
69 std::vector<size_t> frag_lengths;
70
71 // Place all parameter sets at the front of buffer.
72 if (is_keyframe) {
73 size_t param_set_size = 0;
74 const uint8_t* param_set = nullptr;
75 for (size_t i = 0; i < param_set_count; ++i) {
76 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
77 description, i, &param_set, &param_set_size, nullptr, nullptr);
78 if (status != noErr) {
79 LOG(LS_ERROR) << "Failed to get parameter set.";
80 return false;
81 }
82 // Update buffer.
83 annexb_buffer->AppendData(kAnnexBHeaderBytes, sizeof(kAnnexBHeaderBytes));
84 annexb_buffer->AppendData(reinterpret_cast<const char*>(param_set),
85 param_set_size);
86 // Update fragmentation.
87 frag_offsets.push_back(nalu_offset + sizeof(kAnnexBHeaderBytes));
88 frag_lengths.push_back(param_set_size);
89 nalu_offset += sizeof(kAnnexBHeaderBytes) + param_set_size;
90 }
91 }
92
93 // Get block buffer from the sample buffer.
94 CMBlockBufferRef block_buffer =
95 CMSampleBufferGetDataBuffer(avcc_sample_buffer);
96 if (block_buffer == nullptr) {
97 LOG(LS_ERROR) << "Failed to get sample buffer's block buffer.";
98 return false;
99 }
100 CMBlockBufferRef contiguous_buffer = nullptr;
101 // Make sure block buffer is contiguous.
102 if (!CMBlockBufferIsRangeContiguous(block_buffer, 0, 0)) {
103 status = CMBlockBufferCreateContiguous(
104 nullptr, block_buffer, nullptr, nullptr, 0, 0, 0, &contiguous_buffer);
105 if (status != noErr) {
106 LOG(LS_ERROR) << "Failed to flatten non-contiguous block buffer: "
107 << status;
108 return false;
109 }
110 } else {
111 contiguous_buffer = block_buffer;
112 // Retain to make cleanup easier.
113 CFRetain(contiguous_buffer);
114 block_buffer = nullptr;
115 }
116
117 // Now copy the actual data.
118 char* data_ptr = nullptr;
119 size_t block_buffer_size = CMBlockBufferGetDataLength(contiguous_buffer);
120 status = CMBlockBufferGetDataPointer(contiguous_buffer, 0, nullptr, nullptr,
121 &data_ptr);
122 if (status != noErr) {
123 LOG(LS_ERROR) << "Failed to get block buffer data.";
124 CFRelease(contiguous_buffer);
125 return false;
126 }
127 size_t bytes_remaining = block_buffer_size;
128 while (bytes_remaining > 0) {
129 // The size type here must match |nalu_header_size|, we expect 4 bytes.
130 // Read the length of the next packet of data. Must convert from big endian
131 // to host endian.
132 RTC_DCHECK_GE(bytes_remaining, (size_t)nalu_header_size);
133 uint32_t* uint32_data_ptr = reinterpret_cast<uint32_t*>(data_ptr);
134 uint32_t packet_size = CFSwapInt32BigToHost(*uint32_data_ptr);
135 // Update buffer.
136 annexb_buffer->AppendData(kAnnexBHeaderBytes, sizeof(kAnnexBHeaderBytes));
137 annexb_buffer->AppendData(data_ptr + nalu_header_size, packet_size);
138 // Update fragmentation.
139 frag_offsets.push_back(nalu_offset + sizeof(kAnnexBHeaderBytes));
140 frag_lengths.push_back(packet_size);
141 nalu_offset += sizeof(kAnnexBHeaderBytes) + packet_size;
142
143 size_t bytes_written = packet_size + nalu_header_size;
144 bytes_remaining -= bytes_written;
145 data_ptr += bytes_written;
146 }
147 RTC_DCHECK_EQ(bytes_remaining, (size_t)0);
148
149 std::unique_ptr<webrtc::RTPFragmentationHeader> header;
150 header.reset(new webrtc::RTPFragmentationHeader());
151 header->VerifyAndAllocateFragmentationHeader(frag_offsets.size());
152 RTC_DCHECK_EQ(frag_lengths.size(), frag_offsets.size());
153 for (size_t i = 0; i < frag_offsets.size(); ++i) {
154 header->fragmentationOffset[i] = frag_offsets[i];
155 header->fragmentationLength[i] = frag_lengths[i];
156 header->fragmentationPlType[i] = 0;
157 header->fragmentationTimeDiff[i] = 0;
158 }
159 *out_header = header.release();
160 CFRelease(contiguous_buffer);
161 return true;
162 }
163
164 bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
165 size_t annexb_buffer_size,
166 CMVideoFormatDescriptionRef video_format,
167 CMSampleBufferRef* out_sample_buffer) {
168 RTC_DCHECK(annexb_buffer);
169 RTC_DCHECK(out_sample_buffer);
170 RTC_DCHECK(video_format);
171 *out_sample_buffer = nullptr;
172
173 AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
174 if (H264AnnexBBufferHasVideoFormatDescription(annexb_buffer,
175 annexb_buffer_size)) {
176 // Advance past the SPS and PPS.
177 const uint8_t* data = nullptr;
178 size_t data_len = 0;
179 if (!reader.ReadNalu(&data, &data_len)) {
180 LOG(LS_ERROR) << "Failed to read SPS";
181 return false;
182 }
183 if (!reader.ReadNalu(&data, &data_len)) {
184 LOG(LS_ERROR) << "Failed to read PPS";
185 return false;
186 }
187 }
188
189 // Allocate memory as a block buffer.
190 // TODO(tkchin): figure out how to use a pool.
191 CMBlockBufferRef block_buffer = nullptr;
192 OSStatus status = CMBlockBufferCreateWithMemoryBlock(
193 nullptr, nullptr, reader.BytesRemaining(), nullptr, nullptr, 0,
194 reader.BytesRemaining(), kCMBlockBufferAssureMemoryNowFlag,
195 &block_buffer);
196 if (status != kCMBlockBufferNoErr) {
197 LOG(LS_ERROR) << "Failed to create block buffer.";
198 return false;
199 }
200
201 // Make sure block buffer is contiguous.
202 CMBlockBufferRef contiguous_buffer = nullptr;
203 if (!CMBlockBufferIsRangeContiguous(block_buffer, 0, 0)) {
204 status = CMBlockBufferCreateContiguous(
205 nullptr, block_buffer, nullptr, nullptr, 0, 0, 0, &contiguous_buffer);
206 if (status != noErr) {
207 LOG(LS_ERROR) << "Failed to flatten non-contiguous block buffer: "
208 << status;
209 CFRelease(block_buffer);
210 return false;
211 }
212 } else {
213 contiguous_buffer = block_buffer;
214 block_buffer = nullptr;
215 }
216
217 // Get a raw pointer into allocated memory.
218 size_t block_buffer_size = 0;
219 char* data_ptr = nullptr;
220 status = CMBlockBufferGetDataPointer(contiguous_buffer, 0, nullptr,
221 &block_buffer_size, &data_ptr);
222 if (status != kCMBlockBufferNoErr) {
223 LOG(LS_ERROR) << "Failed to get block buffer data pointer.";
224 CFRelease(contiguous_buffer);
225 return false;
226 }
227 RTC_DCHECK(block_buffer_size == reader.BytesRemaining());
228
229 // Write Avcc NALUs into block buffer memory.
230 AvccBufferWriter writer(reinterpret_cast<uint8_t*>(data_ptr),
231 block_buffer_size);
232 while (reader.BytesRemaining() > 0) {
233 const uint8_t* nalu_data_ptr = nullptr;
234 size_t nalu_data_size = 0;
235 if (reader.ReadNalu(&nalu_data_ptr, &nalu_data_size)) {
236 writer.WriteNalu(nalu_data_ptr, nalu_data_size);
237 }
238 }
239
240 // Create sample buffer.
241 status = CMSampleBufferCreate(nullptr, contiguous_buffer, true, nullptr,
242 nullptr, video_format, 1, 0, nullptr, 0,
243 nullptr, out_sample_buffer);
244 if (status != noErr) {
245 LOG(LS_ERROR) << "Failed to create sample buffer.";
246 CFRelease(contiguous_buffer);
247 return false;
248 }
249 CFRelease(contiguous_buffer);
250 return true;
251 }
252
253 bool H264AnnexBBufferHasVideoFormatDescription(const uint8_t* annexb_buffer,
254 size_t annexb_buffer_size) {
255 RTC_DCHECK(annexb_buffer);
256 RTC_DCHECK_GT(annexb_buffer_size, 4u);
257
258 // The buffer we receive via RTP has 00 00 00 01 start code artifically
259 // embedded by the RTP depacketizer. Extract NALU information.
260 // TODO(tkchin): handle potential case where sps and pps are delivered
261 // separately.
262 NaluType first_nalu_type = ParseNaluType(annexb_buffer[4]);
263 bool is_first_nalu_type_sps = first_nalu_type == kSps;
264 if (is_first_nalu_type_sps)
265 return true;
266 bool is_first_nalu_type_aud = first_nalu_type == kAud;
267 // Start code + access unit delimiter + start code = 4 + 2 + 4 = 10.
268 if (!is_first_nalu_type_aud || annexb_buffer_size <= 10u)
269 return false;
270 NaluType second_nalu_type = ParseNaluType(annexb_buffer[10]);
271 bool is_second_nalu_type_sps = second_nalu_type == kSps;
272 return is_second_nalu_type_sps;
273 }
274
275 CMVideoFormatDescriptionRef CreateVideoFormatDescription(
276 const uint8_t* annexb_buffer,
277 size_t annexb_buffer_size) {
278 if (!H264AnnexBBufferHasVideoFormatDescription(annexb_buffer,
279 annexb_buffer_size)) {
280 return nullptr;
281 }
282 AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
283 CMVideoFormatDescriptionRef description = nullptr;
284 OSStatus status = noErr;
285 // Parse the SPS and PPS into a CMVideoFormatDescription.
286 const uint8_t* param_set_ptrs[2] = {};
287 size_t param_set_sizes[2] = {};
288 // Skip AUD.
289 if (ParseNaluType(annexb_buffer[4]) == kAud) {
290 if (!reader.ReadNalu(&param_set_ptrs[0], &param_set_sizes[0])) {
291 LOG(LS_ERROR) << "Failed to read AUD";
292 return nullptr;
293 }
294 }
295 if (!reader.ReadNalu(&param_set_ptrs[0], &param_set_sizes[0])) {
296 LOG(LS_ERROR) << "Failed to read SPS";
297 return nullptr;
298 }
299 if (!reader.ReadNalu(&param_set_ptrs[1], &param_set_sizes[1])) {
300 LOG(LS_ERROR) << "Failed to read PPS";
301 return nullptr;
302 }
303 status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
304 kCFAllocatorDefault, 2, param_set_ptrs, param_set_sizes, 4,
305 &description);
306 if (status != noErr) {
307 LOG(LS_ERROR) << "Failed to create video format description.";
308 return nullptr;
309 }
310 return description;
311 }
312
313 AnnexBBufferReader::AnnexBBufferReader(const uint8_t* annexb_buffer,
314 size_t length)
315 : start_(annexb_buffer), offset_(0), next_offset_(0), length_(length) {
316 RTC_DCHECK(annexb_buffer);
317 offset_ = FindNextNaluHeader(start_, length_, 0);
318 next_offset_ =
319 FindNextNaluHeader(start_, length_, offset_ + sizeof(kAnnexBHeaderBytes));
320 }
321
322 bool AnnexBBufferReader::ReadNalu(const uint8_t** out_nalu,
323 size_t* out_length) {
324 RTC_DCHECK(out_nalu);
325 RTC_DCHECK(out_length);
326 *out_nalu = nullptr;
327 *out_length = 0;
328
329 size_t data_offset = offset_ + sizeof(kAnnexBHeaderBytes);
330 if (data_offset > length_) {
331 return false;
332 }
333 *out_nalu = start_ + data_offset;
334 *out_length = next_offset_ - data_offset;
335 offset_ = next_offset_;
336 next_offset_ =
337 FindNextNaluHeader(start_, length_, offset_ + sizeof(kAnnexBHeaderBytes));
338 return true;
339 }
340
341 size_t AnnexBBufferReader::BytesRemaining() const {
342 return length_ - offset_;
343 }
344
345 size_t AnnexBBufferReader::FindNextNaluHeader(const uint8_t* start,
346 size_t length,
347 size_t offset) const {
348 RTC_DCHECK(start);
349 if (offset + sizeof(kAnnexBHeaderBytes) > length) {
350 return length;
351 }
352 // NALUs are separated by an 00 00 00 01 header. Scan the byte stream
353 // starting from the offset for the next such sequence.
354 const uint8_t* current = start + offset;
355 // The loop reads sizeof(kAnnexBHeaderBytes) at a time, so stop when there
356 // aren't enough bytes remaining.
357 const uint8_t* const end = start + length - sizeof(kAnnexBHeaderBytes);
358 while (current < end) {
359 if (current[3] > 1) {
360 current += 4;
361 } else if (current[3] == 1 && current[2] == 0 && current[1] == 0 &&
362 current[0] == 0) {
363 return current - start;
364 } else {
365 ++current;
366 }
367 }
368 return length;
369 }
370
371 AvccBufferWriter::AvccBufferWriter(uint8_t* const avcc_buffer, size_t length)
372 : start_(avcc_buffer), offset_(0), length_(length) {
373 RTC_DCHECK(avcc_buffer);
374 }
375
376 bool AvccBufferWriter::WriteNalu(const uint8_t* data, size_t data_size) {
377 // Check if we can write this length of data.
378 if (data_size + kAvccHeaderByteSize > BytesRemaining()) {
379 return false;
380 }
381 // Write length header, which needs to be big endian.
382 uint32_t big_endian_length = CFSwapInt32HostToBig(data_size);
383 memcpy(start_ + offset_, &big_endian_length, sizeof(big_endian_length));
384 offset_ += sizeof(big_endian_length);
385 // Write data.
386 memcpy(start_ + offset_, data, data_size);
387 offset_ += data_size;
388 return true;
389 }
390
391 size_t AvccBufferWriter::BytesRemaining() const {
392 return length_ - offset_;
393 }
394
395 } // namespace webrtc
396
397 #endif // defined(WEBRTC_VIDEO_TOOLBOX_SUPPORTED)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698