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