OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "media/base/mac/videotoolbox_helpers.h" | |
6 | |
7 #include <array> | |
8 #include <vector> | |
9 | |
10 #include "base/big_endian.h" | |
11 #include "base/memory/scoped_ptr.h" | |
12 | |
13 namespace media { | |
14 | |
15 namespace video_toolbox { | |
16 | |
17 base::ScopedCFTypeRef<CFDictionaryRef> | |
18 DictionaryWithKeysAndValues(CFTypeRef* keys, CFTypeRef* values, size_t size) { | |
19 return base::ScopedCFTypeRef<CFDictionaryRef>(CFDictionaryCreate( | |
20 kCFAllocatorDefault, keys, values, size, &kCFTypeDictionaryKeyCallBacks, | |
21 &kCFTypeDictionaryValueCallBacks)); | |
22 } | |
23 | |
24 base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue(CFTypeRef key, | |
25 CFTypeRef value) { | |
26 CFTypeRef keys[1] = {key}; | |
27 CFTypeRef values[1] = {value}; | |
28 return DictionaryWithKeysAndValues(keys, values, 1); | |
29 } | |
30 | |
31 base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegers(const int* v, size_t size) { | |
32 std::vector<CFNumberRef> numbers; | |
33 numbers.reserve(size); | |
34 for (const int* end = v + size; v < end; ++v) | |
35 numbers.push_back(CFNumberCreate(nullptr, kCFNumberSInt32Type, v)); | |
36 base::ScopedCFTypeRef<CFArrayRef> array(CFArrayCreate( | |
37 kCFAllocatorDefault, reinterpret_cast<const void**>(&numbers[0]), | |
38 numbers.size(), &kCFTypeArrayCallBacks)); | |
39 for (auto& number : numbers) { | |
40 CFRelease(number); | |
41 } | |
42 return array; | |
43 } | |
44 | |
45 base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegerAndFloat(int int_val, | |
46 float float_val) { | |
47 std::array<CFNumberRef, 2> numbers = { | |
48 {CFNumberCreate(nullptr, kCFNumberSInt32Type, &int_val), | |
49 CFNumberCreate(nullptr, kCFNumberFloat32Type, &float_val)}}; | |
50 base::ScopedCFTypeRef<CFArrayRef> array(CFArrayCreate( | |
51 kCFAllocatorDefault, reinterpret_cast<const void**>(numbers.data()), | |
52 numbers.size(), &kCFTypeArrayCallBacks)); | |
53 for (auto& number : numbers) | |
54 CFRelease(number); | |
55 return array; | |
56 } | |
57 | |
58 // Wrapper class for writing AnnexBBuffer output into. | |
59 class AnnexBBuffer { | |
60 public: | |
61 virtual bool Reserve(size_t size) = 0; | |
62 virtual void Append(const char* s, size_t n) = 0; | |
63 virtual size_t GetReservedSize() const = 0; | |
64 }; | |
65 | |
66 class RawAnnexBBuffer : public AnnexBBuffer { | |
67 public: | |
68 RawAnnexBBuffer(char* annexb_buffer, size_t annexb_buffer_size) | |
69 : annexb_buffer_(annexb_buffer), | |
70 annexb_buffer_size_(annexb_buffer_size), | |
71 annexb_buffer_offset_(0) {} | |
72 bool Reserve(size_t size) override { | |
73 reserved_size_ = size; | |
74 return size <= annexb_buffer_size_; | |
75 } | |
76 void Append(const char* s, size_t n) override { | |
77 memcpy(annexb_buffer_ + annexb_buffer_offset_, s, n); | |
78 annexb_buffer_offset_ += n; | |
79 DCHECK_GE(reserved_size_, annexb_buffer_offset_); | |
80 } | |
81 size_t GetReservedSize() const override { return reserved_size_; } | |
82 | |
83 private: | |
84 char* annexb_buffer_; | |
85 size_t annexb_buffer_size_; | |
86 size_t annexb_buffer_offset_; | |
87 size_t reserved_size_; | |
88 | |
89 DISALLOW_IMPLICIT_CONSTRUCTORS(RawAnnexBBuffer); | |
90 }; | |
91 | |
92 class StringAnnexBBuffer : public AnnexBBuffer { | |
93 public: | |
94 explicit StringAnnexBBuffer(std::string* str_annexb_buffer) | |
95 : str_annexb_buffer_(str_annexb_buffer) {} | |
96 bool Reserve(size_t size) override { | |
97 str_annexb_buffer_->reserve(size); | |
98 return true; | |
99 } | |
100 void Append(const char* s, size_t n) override { | |
101 str_annexb_buffer_->append(s, n); | |
102 } | |
103 size_t GetReservedSize() const override { return str_annexb_buffer_->size(); } | |
104 | |
105 private: | |
106 std::string* str_annexb_buffer_; | |
107 DISALLOW_IMPLICIT_CONSTRUCTORS(StringAnnexBBuffer); | |
108 }; | |
109 | |
110 template <typename NalSizeType> | |
111 void CopyNalsToAnnexB(char* avcc_buffer, | |
112 const size_t avcc_size, | |
113 AnnexBBuffer* annexb_buffer) { | |
114 static_assert(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 || | |
115 sizeof(NalSizeType) == 4, | |
116 "NAL size type has unsupported size"); | |
117 static const char startcode_3[3] = {0, 0, 1}; | |
118 DCHECK(avcc_buffer); | |
119 DCHECK(annexb_buffer); | |
120 size_t bytes_left = avcc_size; | |
121 while (bytes_left > 0) { | |
122 DCHECK_GT(bytes_left, sizeof(NalSizeType)); | |
123 NalSizeType nal_size; | |
124 base::ReadBigEndian(avcc_buffer, &nal_size); | |
125 bytes_left -= sizeof(NalSizeType); | |
126 avcc_buffer += sizeof(NalSizeType); | |
127 | |
128 DCHECK_GE(bytes_left, nal_size); | |
129 annexb_buffer->Append(startcode_3, sizeof(startcode_3)); | |
130 annexb_buffer->Append(avcc_buffer, nal_size); | |
131 bytes_left -= nal_size; | |
132 avcc_buffer += nal_size; | |
133 } | |
134 } | |
135 | |
136 bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, | |
137 AnnexBBuffer* annexb_buffer, | |
138 bool keyframe) { | |
139 // Perform two pass, one to figure out the total output size, and another to | |
140 // copy the data after having performed a single output allocation. Note that | |
141 // we'll allocate a bit more because we'll count 4 bytes instead of 3 for | |
142 // video NALs. | |
143 OSStatus status; | |
144 | |
145 // Get the sample buffer's block buffer and format description. | |
146 auto bb = CoreMediaGlue::CMSampleBufferGetDataBuffer(sbuf); | |
147 DCHECK(bb); | |
148 auto fdesc = CoreMediaGlue::CMSampleBufferGetFormatDescription(sbuf); | |
149 DCHECK(fdesc); | |
150 | |
151 size_t bb_size = CoreMediaGlue::CMBlockBufferGetDataLength(bb); | |
152 size_t total_bytes = bb_size; | |
153 | |
154 size_t pset_count; | |
155 int nal_size_field_bytes; | |
156 status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
157 fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes); | |
158 if (status == | |
159 CoreMediaGlue::kCMFormatDescriptionBridgeError_InvalidParameter) { | |
160 DLOG(WARNING) << " assuming 2 parameter sets and 4 bytes NAL length header"; | |
161 pset_count = 2; | |
162 nal_size_field_bytes = 4; | |
163 } else if (status != noErr) { | |
164 DLOG(ERROR) | |
165 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
166 << status; | |
167 return false; | |
168 } | |
169 | |
170 if (keyframe) { | |
171 const uint8_t* pset; | |
172 size_t pset_size; | |
173 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
174 status = | |
175 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
176 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); | |
177 if (status != noErr) { | |
178 DLOG(ERROR) | |
179 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
180 << status; | |
181 return false; | |
182 } | |
183 total_bytes += pset_size + nal_size_field_bytes; | |
184 } | |
185 } | |
186 | |
187 if (!annexb_buffer->Reserve(total_bytes)) { | |
188 DLOG(ERROR) << "Cannot fit encode output into bitstream buffer. Requested:" | |
189 << total_bytes; | |
190 return false; | |
191 } | |
192 | |
193 // Copy all parameter sets before keyframes. | |
194 if (keyframe) { | |
195 const uint8_t* pset; | |
196 size_t pset_size; | |
197 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
198 status = | |
199 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
200 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); | |
201 if (status != noErr) { | |
202 DLOG(ERROR) | |
203 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
204 << status; | |
205 return false; | |
206 } | |
207 static const char startcode_4[4] = {0, 0, 0, 1}; | |
208 annexb_buffer->Append(startcode_4, sizeof(startcode_4)); | |
209 annexb_buffer->Append(reinterpret_cast<const char*>(pset), pset_size); | |
210 } | |
211 } | |
212 | |
213 // Block buffers can be composed of non-contiguous chunks. For the sake of | |
214 // keeping this code simple, flatten non-contiguous block buffers. | |
215 base::ScopedCFTypeRef<CoreMediaGlue::CMBlockBufferRef> contiguous_bb( | |
216 bb, base::scoped_policy::RETAIN); | |
217 if (!CoreMediaGlue::CMBlockBufferIsRangeContiguous(bb, 0, 0)) { | |
218 contiguous_bb.reset(); | |
219 status = CoreMediaGlue::CMBlockBufferCreateContiguous( | |
220 kCFAllocatorDefault, bb, kCFAllocatorDefault, nullptr, 0, 0, 0, | |
221 contiguous_bb.InitializeInto()); | |
222 if (status != noErr) { | |
223 DLOG(ERROR) << " CMBlockBufferCreateContiguous failed: " << status; | |
224 return false; | |
225 } | |
226 } | |
227 | |
228 // Copy all the NAL units. In the process convert them from AVCC format | |
229 // (length header) to AnnexB format (start code). | |
230 char* bb_data; | |
231 status = CoreMediaGlue::CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr, | |
232 nullptr, &bb_data); | |
233 if (status != noErr) { | |
234 DLOG(ERROR) << " CMBlockBufferGetDataPointer failed: " << status; | |
235 return false; | |
236 } | |
237 | |
238 if (nal_size_field_bytes == 1) { | |
239 CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer); | |
240 } else if (nal_size_field_bytes == 2) { | |
241 CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer); | |
242 } else if (nal_size_field_bytes == 4) { | |
243 CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer); | |
244 } else { | |
245 NOTREACHED(); | |
246 } | |
247 return true; | |
248 } | |
249 | |
250 bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, | |
251 bool keyframe, | |
252 std::string* annexb_buffer) { | |
253 StringAnnexBBuffer buffer(annexb_buffer); | |
254 return CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); | |
255 } | |
256 | |
257 bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, | |
258 bool keyframe, | |
259 size_t annexb_buffer_size, | |
260 char* annexb_buffer, | |
261 size_t* used_buffer_size) { | |
262 RawAnnexBBuffer buffer(annexb_buffer, annexb_buffer_size); | |
263 const bool copy_rv = CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); | |
264 *used_buffer_size = buffer.GetReservedSize(); | |
265 return copy_rv; | |
266 } | |
267 | |
268 SessionPropertySetter::SessionPropertySetter( | |
269 base::ScopedCFTypeRef<VideoToolboxGlue::VTCompressionSessionRef> session, | |
270 const VideoToolboxGlue* const glue) | |
271 : session_(session), glue_(glue) {} | |
272 | |
273 SessionPropertySetter::~SessionPropertySetter() {} | |
274 | |
275 bool SessionPropertySetter::Set(CFStringRef key, int32_t value) { | |
276 DCHECK(session_); | |
277 DCHECK(glue_); | |
278 base::ScopedCFTypeRef<CFNumberRef> cfvalue( | |
279 CFNumberCreate(nullptr, kCFNumberSInt32Type, &value)); | |
280 return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; | |
281 } | |
282 | |
283 bool SessionPropertySetter::Set(CFStringRef key, bool value) { | |
284 DCHECK(session_); | |
285 DCHECK(glue_); | |
286 CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse; | |
287 return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; | |
288 } | |
289 | |
290 bool SessionPropertySetter::Set(CFStringRef key, CFStringRef value) { | |
291 DCHECK(session_); | |
292 DCHECK(glue_); | |
293 return glue_->VTSessionSetProperty(session_, key, value) == noErr; | |
294 } | |
295 | |
296 bool SessionPropertySetter::Set(CFStringRef key, CFArrayRef value) { | |
297 DCHECK(session_); | |
298 DCHECK(glue_); | |
299 return glue_->VTSessionSetProperty(session_, key, value) == noErr; | |
300 } | |
301 | |
302 } // namespace video_toolbox | |
303 | |
304 } // namespace media | |
OLD | NEW |