OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/logging.h" | 5 #include "base/logging.h" |
6 #include "services/media/framework_ffmpeg/av_codec_context.h" | 6 #include "services/media/framework_ffmpeg/av_codec_context.h" |
7 #include "services/media/framework_ffmpeg/ffmpeg_init.h" | 7 #include "services/media/framework_ffmpeg/ffmpeg_init.h" |
8 extern "C" { | 8 extern "C" { |
9 #include "third_party/ffmpeg/libavformat/avformat.h" | 9 #include "third_party/ffmpeg/libavformat/avformat.h" |
10 } | 10 } |
11 | 11 |
12 // Ffmeg defines this...undefine. | 12 // Ffmeg defines this...undefine. |
13 #undef PixelFormat | 13 #undef PixelFormat |
14 | 14 |
15 namespace mojo { | 15 namespace mojo { |
16 namespace media { | 16 namespace media { |
17 | 17 |
18 namespace { | 18 namespace { |
19 | 19 |
20 // Converts an AVSampleFormat into an LpcmStreamType::SampleFormat. | 20 // Converts an AVSampleFormat into an AudioStreamType::SampleFormat. |
21 LpcmStreamType::SampleFormat Convert(AVSampleFormat av_sample_format) { | 21 AudioStreamType::SampleFormat Convert(AVSampleFormat av_sample_format) { |
22 switch (av_sample_format) { | 22 switch (av_sample_format) { |
23 case AV_SAMPLE_FMT_U8: | 23 case AV_SAMPLE_FMT_U8: |
24 case AV_SAMPLE_FMT_U8P: | 24 case AV_SAMPLE_FMT_U8P: |
25 return LpcmStreamType::SampleFormat::kUnsigned8; | 25 return AudioStreamType::SampleFormat::kUnsigned8; |
26 case AV_SAMPLE_FMT_S16: | 26 case AV_SAMPLE_FMT_S16: |
27 case AV_SAMPLE_FMT_S16P: | 27 case AV_SAMPLE_FMT_S16P: |
28 return LpcmStreamType::SampleFormat::kSigned16; | 28 return AudioStreamType::SampleFormat::kSigned16; |
29 case AV_SAMPLE_FMT_S32: | 29 case AV_SAMPLE_FMT_S32: |
30 case AV_SAMPLE_FMT_S32P: | 30 case AV_SAMPLE_FMT_S32P: |
31 return LpcmStreamType::SampleFormat::kSigned24In32; | 31 return AudioStreamType::SampleFormat::kSigned24In32; |
32 case AV_SAMPLE_FMT_FLT: | 32 case AV_SAMPLE_FMT_FLT: |
33 case AV_SAMPLE_FMT_FLTP: | 33 case AV_SAMPLE_FMT_FLTP: |
34 return LpcmStreamType::SampleFormat::kFloat; | 34 return AudioStreamType::SampleFormat::kFloat; |
35 case AV_SAMPLE_FMT_NONE: | 35 case AV_SAMPLE_FMT_NONE: |
36 case AV_SAMPLE_FMT_DBL: | 36 case AV_SAMPLE_FMT_DBL: |
37 case AV_SAMPLE_FMT_DBLP: | 37 case AV_SAMPLE_FMT_DBLP: |
38 case AV_SAMPLE_FMT_NB: | 38 case AV_SAMPLE_FMT_NB: |
39 default: | 39 default: |
40 NOTREACHED() << "unsupported av_sample_format " << av_sample_format; | 40 LOG(ERROR) << "unsupported av_sample_format " << av_sample_format; |
41 return LpcmStreamType::SampleFormat::kUnknown; | 41 abort(); |
42 } | 42 } |
43 } | 43 } |
44 | 44 |
45 // Copies a buffer from Bytes into context->extradata. The result is malloc'ed | 45 // Copies a buffer from Bytes into context->extradata. The result is malloc'ed |
46 // and must be freed. | 46 // and must be freed. |
47 void ExtraDataFromBytes(const Bytes& bytes, const AvCodecContextPtr& context) { | 47 void ExtraDataFromBytes(const Bytes& bytes, const AvCodecContextPtr& context) { |
48 size_t byte_count = bytes.size(); | 48 size_t byte_count = bytes.size(); |
49 uint8_t* copy = reinterpret_cast<uint8_t*>(malloc(byte_count)); | 49 uint8_t* copy = reinterpret_cast<uint8_t*>(malloc(byte_count)); |
50 std::memcpy(copy, bytes.data(), byte_count); | 50 std::memcpy(copy, bytes.data(), byte_count); |
51 context->extradata = copy; | 51 context->extradata = copy; |
52 context->extradata_size = byte_count; | 52 context->extradata_size = byte_count; |
53 } | 53 } |
54 | 54 |
55 // Creates a StreamType from an AVCodecContext describing an LPCM type. | 55 // Creates a StreamType from an AVCodecContext describing an LPCM type. |
56 std::unique_ptr<StreamType> StreamTypeFromLpcmCodecContext( | 56 std::unique_ptr<StreamType> StreamTypeFromLpcmCodecContext( |
57 const AVCodecContext& from) { | 57 const AVCodecContext& from) { |
58 return LpcmStreamType::Create(Convert(from.sample_fmt), from.channels, | 58 return AudioStreamType::Create(StreamType::kAudioEncodingLpcm, nullptr, |
59 from.sample_rate); | 59 Convert(from.sample_fmt), from.channels, |
| 60 from.sample_rate); |
60 } | 61 } |
61 | 62 |
62 // Creates a StreamType from an AVCodecContext describing a compressed audio | 63 // Creates a StreamType from an AVCodecContext describing a compressed audio |
63 // type. | 64 // type. |
64 std::unique_ptr<StreamType> StreamTypeFromCompressedAudioCodecContext( | 65 std::unique_ptr<StreamType> StreamTypeFromCompressedAudioCodecContext( |
65 const AVCodecContext& from) { | 66 const AVCodecContext& from) { |
66 CompressedAudioStreamType::AudioEncoding encoding; | 67 const char* encoding; |
67 switch (from.codec_id) { | 68 switch (from.codec_id) { |
68 case CODEC_ID_VORBIS: | 69 case CODEC_ID_VORBIS: |
69 encoding = CompressedAudioStreamType::AudioEncoding::kVorbis; | 70 encoding = StreamType::kAudioEncodingVorbis; |
70 break; | 71 break; |
71 default: | 72 default: |
72 encoding = CompressedAudioStreamType::AudioEncoding::kUnknown; | 73 LOG(ERROR) << "unsupported codec_id " << from.codec_id; |
73 break; | 74 abort(); |
74 } | 75 } |
75 | 76 |
76 return CompressedAudioStreamType::Create( | 77 return AudioStreamType::Create( |
77 encoding, Convert(from.sample_fmt), from.channels, from.sample_rate, | 78 encoding, from.extradata_size == 0 |
78 from.extradata_size == 0 ? nullptr : Bytes::Create(from.extradata, | 79 ? nullptr |
79 from.extradata_size)); | 80 : Bytes::Create(from.extradata, from.extradata_size), |
| 81 Convert(from.sample_fmt), from.channels, from.sample_rate); |
80 } | 82 } |
81 | 83 |
82 // Converts AVColorSpace and AVColorRange to ColorSpace. | 84 // Converts AVColorSpace and AVColorRange to ColorSpace. |
83 VideoStreamType::ColorSpace ColorSpaceFromAVColorSpaceAndRange( | 85 VideoStreamType::ColorSpace ColorSpaceFromAVColorSpaceAndRange( |
84 AVColorSpace color_space, | 86 AVColorSpace color_space, |
85 AVColorRange color_range) { | 87 AVColorRange color_range) { |
86 // TODO(dalesat): Blindly copied from Chromium. | 88 // TODO(dalesat): Blindly copied from Chromium. |
87 if (color_range == AVCOL_RANGE_JPEG) { | 89 if (color_range == AVCOL_RANGE_JPEG) { |
88 return VideoStreamType::ColorSpace::kJpeg; | 90 return VideoStreamType::ColorSpace::kJpeg; |
89 } | 91 } |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
177 case VideoStreamType::PixelFormat::kMjpeg: | 179 case VideoStreamType::PixelFormat::kMjpeg: |
178 case VideoStreamType::PixelFormat::kMt21: | 180 case VideoStreamType::PixelFormat::kMt21: |
179 default: | 181 default: |
180 return AV_PIX_FMT_NONE; | 182 return AV_PIX_FMT_NONE; |
181 } | 183 } |
182 } | 184 } |
183 | 185 |
184 // Creates a StreamType from an AVCodecContext describing a video type. | 186 // Creates a StreamType from an AVCodecContext describing a video type. |
185 std::unique_ptr<StreamType> StreamTypeFromVideoCodecContext( | 187 std::unique_ptr<StreamType> StreamTypeFromVideoCodecContext( |
186 const AVCodecContext& from) { | 188 const AVCodecContext& from) { |
187 VideoStreamType::VideoEncoding encoding; | 189 const char* encoding; |
188 switch (from.codec_id) { | 190 switch (from.codec_id) { |
189 case AV_CODEC_ID_THEORA: | 191 case AV_CODEC_ID_THEORA: |
190 encoding = VideoStreamType::VideoEncoding::kTheora; | 192 encoding = StreamType::kVideoEncodingTheora; |
191 break; | |
192 case CODEC_ID_VP8: | |
193 encoding = VideoStreamType::VideoEncoding::kVp8; | |
194 break; | 193 break; |
195 default: | 194 default: |
196 encoding = VideoStreamType::VideoEncoding::kUnknown; | 195 LOG(ERROR) << "unsupported codec_id " << from.codec_id; |
197 break; | 196 abort(); |
198 } | 197 } |
199 | 198 |
200 return VideoStreamType::Create( | 199 return VideoStreamType::Create( |
201 encoding, VideoStreamType::VideoProfile::kNotApplicable, | 200 encoding, from.extradata_size == 0 |
| 201 ? nullptr |
| 202 : Bytes::Create(from.extradata, from.extradata_size), |
| 203 VideoStreamType::VideoProfile::kNotApplicable, |
202 PixelFormatFromAVPixelFormat(from.pix_fmt), | 204 PixelFormatFromAVPixelFormat(from.pix_fmt), |
203 ColorSpaceFromAVColorSpaceAndRange(from.colorspace, from.color_range), | 205 ColorSpaceFromAVColorSpaceAndRange(from.colorspace, from.color_range), |
204 from.width, from.height, from.coded_width, from.coded_height, | 206 from.width, from.height, from.coded_width, from.coded_height); |
205 from.extradata_size == 0 ? nullptr : Bytes::Create(from.extradata, | |
206 from.extradata_size)); | |
207 } | 207 } |
208 | 208 |
209 // Creates a StreamType from an AVCodecContext describing a data type. | 209 // Creates a StreamType from an AVCodecContext describing a data type. |
210 std::unique_ptr<StreamType> StreamTypeFromDataCodecContext( | 210 std::unique_ptr<StreamType> StreamTypeFromDataCodecContext( |
211 const AVCodecContext& from) { | 211 const AVCodecContext& from) { |
212 // TODO(dalesat): Implement. | 212 // TODO(dalesat): Implement. |
213 return StreamType::Create(StreamType::Scheme::kUnknown); | 213 LOG(ERROR) << "StreamTypeFromDataCodecContext not implemented"; |
| 214 abort(); |
214 } | 215 } |
215 | 216 |
216 // Creates a StreamType from an AVCodecContext describing a subtitle type. | 217 // Creates a StreamType from an AVCodecContext describing a subtitle type. |
217 std::unique_ptr<StreamType> StreamTypeFromSubtitleCodecContext( | 218 std::unique_ptr<StreamType> StreamTypeFromSubtitleCodecContext( |
218 const AVCodecContext& from) { | 219 const AVCodecContext& from) { |
219 // TODO(dalesat): Implement. | 220 // TODO(dalesat): Implement. |
220 return StreamType::Create(StreamType::Scheme::kUnknown); | 221 LOG(ERROR) << "StreamTypeFromSubtitleCodecContext not implemented"; |
| 222 abort(); |
221 } | 223 } |
222 | 224 |
223 // Creates an AVCodecContext from LpcmStreamType. | 225 // Creates an AVCodecContext from an AudioStreamType. |
224 AvCodecContextPtr CodecContextFromLpcmDetails( | 226 AvCodecContextPtr AVCodecContextFromAudioStreamType( |
225 const LpcmStreamType& stream_type) { | 227 const AudioStreamType& stream_type) { |
| 228 DCHECK(stream_type.medium() == StreamType::Medium::kAudio); |
| 229 |
226 AVCodecID codec_id; | 230 AVCodecID codec_id; |
227 AVSampleFormat sample_format; | 231 AVSampleFormat sample_format; |
228 | 232 |
229 switch (stream_type.sample_format()) { | 233 if (stream_type.encoding() == StreamType::kAudioEncodingLpcm) { |
230 case LpcmStreamType::SampleFormat::kUnsigned8: | 234 switch (stream_type.sample_format()) { |
231 codec_id = AV_CODEC_ID_PCM_U8; | 235 case AudioStreamType::SampleFormat::kUnsigned8: |
232 sample_format = AV_SAMPLE_FMT_U8; | 236 codec_id = AV_CODEC_ID_PCM_U8; |
233 break; | 237 sample_format = AV_SAMPLE_FMT_U8; |
234 case LpcmStreamType::SampleFormat::kSigned16: | 238 break; |
235 codec_id = AV_CODEC_ID_PCM_S16LE; | 239 case AudioStreamType::SampleFormat::kSigned16: |
236 sample_format = AV_SAMPLE_FMT_S16; | 240 codec_id = AV_CODEC_ID_PCM_S16LE; |
237 break; | 241 sample_format = AV_SAMPLE_FMT_S16; |
238 case LpcmStreamType::SampleFormat::kSigned24In32: | 242 break; |
239 codec_id = AV_CODEC_ID_PCM_S24LE; | 243 case AudioStreamType::SampleFormat::kSigned24In32: |
240 sample_format = AV_SAMPLE_FMT_S32; | 244 codec_id = AV_CODEC_ID_PCM_S24LE; |
241 break; | 245 sample_format = AV_SAMPLE_FMT_S32; |
242 case LpcmStreamType::SampleFormat::kFloat: | 246 break; |
243 codec_id = AV_CODEC_ID_PCM_F32LE; | 247 case AudioStreamType::SampleFormat::kFloat: |
244 sample_format = AV_SAMPLE_FMT_FLT; | 248 codec_id = AV_CODEC_ID_PCM_F32LE; |
245 break; | 249 sample_format = AV_SAMPLE_FMT_FLT; |
246 default: | 250 break; |
247 return nullptr; | 251 default: |
| 252 LOG(ERROR) << "unsupported sample format"; |
| 253 abort(); |
| 254 } |
| 255 } else if (stream_type.encoding() == StreamType::kAudioEncodingVorbis) { |
| 256 codec_id = AV_CODEC_ID_VORBIS; |
| 257 sample_format = AV_SAMPLE_FMT_S16; |
| 258 } else { |
| 259 LOG(ERROR) << "unsupported encoding " << stream_type.encoding(); |
| 260 abort(); |
248 } | 261 } |
249 | 262 |
250 AvCodecContextPtr context(avcodec_alloc_context3(nullptr)); | 263 AvCodecContextPtr context(avcodec_alloc_context3(nullptr)); |
251 | |
252 context->codec_type = AVMEDIA_TYPE_AUDIO; | |
253 context->codec_id = codec_id; | |
254 context->sample_fmt = sample_format; | |
255 context->channels = stream_type.channels(); | |
256 context->sample_rate = stream_type.frames_per_second(); | |
257 | |
258 return context; | |
259 } | |
260 | |
261 // Creates an AVCodecContext from CompressedAudioStreamType. | |
262 AvCodecContextPtr AVCodecContextFromCompressedAudioStreamType( | |
263 const CompressedAudioStreamType& stream_type) { | |
264 AVCodecID codec_id = AV_CODEC_ID_NONE; | |
265 AVSampleFormat sample_format; | |
266 | |
267 switch (stream_type.encoding()) { | |
268 case CompressedAudioStreamType::AudioEncoding::kVorbis: | |
269 codec_id = AV_CODEC_ID_VORBIS; | |
270 sample_format = AV_SAMPLE_FMT_S16; | |
271 break; | |
272 default: | |
273 return nullptr; | |
274 } | |
275 | |
276 if (codec_id == AV_CODEC_ID_NONE) { | |
277 return nullptr; | |
278 } | |
279 | |
280 AvCodecContextPtr context(avcodec_alloc_context3(nullptr)); | |
281 | 264 |
282 context->codec_type = AVMEDIA_TYPE_AUDIO; | 265 context->codec_type = AVMEDIA_TYPE_AUDIO; |
283 context->codec_id = codec_id; | 266 context->codec_id = codec_id; |
284 context->sample_fmt = sample_format; | 267 context->sample_fmt = sample_format; |
285 context->channels = stream_type.channels(); | 268 context->channels = stream_type.channels(); |
286 context->sample_rate = stream_type.frames_per_second(); | 269 context->sample_rate = stream_type.frames_per_second(); |
287 | 270 |
288 if (stream_type.encoding_details()) { | 271 if (stream_type.encoding_parameters()) { |
289 ExtraDataFromBytes(*stream_type.encoding_details(), context); | 272 ExtraDataFromBytes(*stream_type.encoding_parameters(), context); |
290 } | 273 } |
291 | 274 |
292 return context; | 275 return context; |
293 } | 276 } |
294 | 277 |
295 // Creats an AVCodecContext from VideoStreamTypeDetails. | 278 // Creats an AVCodecContext from a VideoStreamType. |
296 AvCodecContextPtr AVCodecContextFromVideoStreamType( | 279 AvCodecContextPtr AVCodecContextFromVideoStreamType( |
297 const VideoStreamType& stream_type) { | 280 const VideoStreamType& stream_type) { |
298 AVCodecID codec_id = AV_CODEC_ID_NONE; | 281 AVCodecID codec_id = AV_CODEC_ID_NONE; |
299 | 282 |
300 // TODO(dalesat): codec_id | 283 // TODO(dalesat): codec_id |
301 | 284 |
302 if (codec_id == AV_CODEC_ID_NONE) { | 285 if (codec_id == AV_CODEC_ID_NONE) { |
303 return nullptr; | 286 return nullptr; |
304 } | 287 } |
305 | 288 |
306 AvCodecContextPtr context(avcodec_alloc_context3(nullptr)); | 289 AvCodecContextPtr context(avcodec_alloc_context3(nullptr)); |
307 | 290 |
308 context->codec_type = AVMEDIA_TYPE_VIDEO; | 291 context->codec_type = AVMEDIA_TYPE_VIDEO; |
309 context->codec_id = codec_id; | 292 context->codec_id = codec_id; |
310 context->profile = FfmpegProfileFromVideoProfile(stream_type.profile()); | 293 context->profile = FfmpegProfileFromVideoProfile(stream_type.profile()); |
311 context->pix_fmt = AVPixelFormatFromPixelFormat(stream_type.pixel_format()); | 294 context->pix_fmt = AVPixelFormatFromPixelFormat(stream_type.pixel_format()); |
312 if (stream_type.color_space() == VideoStreamType::ColorSpace::kJpeg) { | 295 if (stream_type.color_space() == VideoStreamType::ColorSpace::kJpeg) { |
313 context->color_range = AVCOL_RANGE_JPEG; | 296 context->color_range = AVCOL_RANGE_JPEG; |
314 } | 297 } |
315 context->coded_width = stream_type.coded_width(); | 298 context->coded_width = stream_type.coded_width(); |
316 context->coded_height = stream_type.coded_height(); | 299 context->coded_height = stream_type.coded_height(); |
317 | 300 |
318 if (stream_type.encoding_details()) { | 301 if (stream_type.encoding_parameters()) { |
319 ExtraDataFromBytes(*stream_type.encoding_details(), context); | 302 ExtraDataFromBytes(*stream_type.encoding_parameters(), context); |
320 } | 303 } |
321 | 304 |
322 return context; | 305 return context; |
323 } | 306 } |
324 | 307 |
| 308 // Creats an AVCodecContext from a TextStreamType. |
| 309 AvCodecContextPtr AVCodecContextFromTextStreamType( |
| 310 const TextStreamType& stream_type) { |
| 311 // TODO(dalesat): Implement. |
| 312 LOG(ERROR) << "AVCodecContextFromTextStreamType not implemented"; |
| 313 abort(); |
| 314 } |
| 315 |
| 316 // Creats an AVCodecContext from a SubpictureStreamType. |
| 317 AvCodecContextPtr AVCodecContextFromSubpictureStreamType( |
| 318 const SubpictureStreamType& stream_type) { |
| 319 // TODO(dalesat): Implement. |
| 320 LOG(ERROR) << "AVCodecContextFromSupictureStreamType not implemented"; |
| 321 abort(); |
| 322 } |
| 323 |
325 } // namespace | 324 } // namespace |
326 | 325 |
327 // static | 326 // static |
328 std::unique_ptr<StreamType> AvCodecContext::GetStreamType( | 327 std::unique_ptr<StreamType> AvCodecContext::GetStreamType( |
329 const AVCodecContext& from) { | 328 const AVCodecContext& from) { |
330 switch (from.codec_type) { | 329 switch (from.codec_type) { |
331 case AVMEDIA_TYPE_AUDIO: | 330 case AVMEDIA_TYPE_AUDIO: |
332 switch (from.codec_id) { | 331 switch (from.codec_id) { |
333 case CODEC_ID_PCM_S16BE: | 332 case CODEC_ID_PCM_S16BE: |
334 case CODEC_ID_PCM_S16LE: | 333 case CODEC_ID_PCM_S16LE: |
(...skipping 12 matching lines...) Expand all Loading... |
347 return StreamTypeFromVideoCodecContext(from); | 346 return StreamTypeFromVideoCodecContext(from); |
348 case AVMEDIA_TYPE_UNKNOWN: | 347 case AVMEDIA_TYPE_UNKNOWN: |
349 // Treated as AVMEDIA_TYPE_DATA. | 348 // Treated as AVMEDIA_TYPE_DATA. |
350 case AVMEDIA_TYPE_DATA: | 349 case AVMEDIA_TYPE_DATA: |
351 return StreamTypeFromDataCodecContext(from); | 350 return StreamTypeFromDataCodecContext(from); |
352 case AVMEDIA_TYPE_SUBTITLE: | 351 case AVMEDIA_TYPE_SUBTITLE: |
353 return StreamTypeFromSubtitleCodecContext(from); | 352 return StreamTypeFromSubtitleCodecContext(from); |
354 case AVMEDIA_TYPE_ATTACHMENT: | 353 case AVMEDIA_TYPE_ATTACHMENT: |
355 case AVMEDIA_TYPE_NB: | 354 case AVMEDIA_TYPE_NB: |
356 default: | 355 default: |
357 return StreamType::Create(StreamType::Scheme::kUnknown); | 356 LOG(ERROR) << "unsupported code type " << from.codec_type; |
| 357 abort(); |
358 } | 358 } |
359 } | 359 } |
360 | 360 |
361 // static | 361 // static |
362 AvCodecContextPtr AvCodecContext::Create(const StreamType& stream_type) { | 362 AvCodecContextPtr AvCodecContext::Create(const StreamType& stream_type) { |
363 InitFfmpeg(); | 363 InitFfmpeg(); |
364 | 364 |
365 switch (stream_type.scheme()) { | 365 switch (stream_type.medium()) { |
366 case StreamType::Scheme::kLpcm: | 366 case StreamType::Medium::kAudio: |
367 return CodecContextFromLpcmDetails(*stream_type.lpcm()); | 367 return AVCodecContextFromAudioStreamType(*stream_type.audio()); |
368 case StreamType::Scheme::kCompressedAudio: | 368 case StreamType::Medium::kVideo: |
369 return AVCodecContextFromCompressedAudioStreamType( | |
370 *stream_type.compressed_audio()); | |
371 case StreamType::Scheme::kVideo: | |
372 return AVCodecContextFromVideoStreamType(*stream_type.video()); | 369 return AVCodecContextFromVideoStreamType(*stream_type.video()); |
| 370 case StreamType::Medium::kText: |
| 371 return AVCodecContextFromTextStreamType(*stream_type.text()); |
| 372 case StreamType::Medium::kSubpicture: |
| 373 return AVCodecContextFromSubpictureStreamType(*stream_type.subpicture()); |
373 default: | 374 default: |
374 return nullptr; | 375 return nullptr; |
375 } | 376 } |
376 } | 377 } |
377 | 378 |
378 } // namespace media | 379 } // namespace media |
379 } // namespace mojo | 380 } // namespace mojo |
OLD | NEW |