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