OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "media/ffmpeg/ffmpeg_common.h" | 5 #include "media/ffmpeg/ffmpeg_common.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "base/sha1.h" | 8 #include "base/sha1.h" |
9 #include "base/strings/string_number_conversions.h" | 9 #include "base/strings/string_number_conversions.h" |
10 #include "base/strings/string_split.h" | 10 #include "base/strings/string_split.h" |
11 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
12 #include "build/build_config.h" | 12 #include "build/build_config.h" |
13 #include "media/base/audio_decoder_config.h" | 13 #include "media/base/audio_decoder_config.h" |
14 #include "media/base/decoder_buffer.h" | 14 #include "media/base/decoder_buffer.h" |
15 #include "media/base/encryption_scheme.h" | 15 #include "media/base/encryption_scheme.h" |
16 #include "media/base/media_util.h" | 16 #include "media/base/media_util.h" |
17 #include "media/base/video_decoder_config.h" | 17 #include "media/base/video_decoder_config.h" |
18 #include "media/base/video_util.h" | 18 #include "media/base/video_util.h" |
19 #include "media/media_features.h" | 19 #include "media/media_features.h" |
20 | 20 |
21 namespace media { | 21 namespace media { |
22 | 22 |
23 namespace { | 23 namespace { |
24 | 24 |
25 gfx::Size GetCodedSize(AVCodecContext* context) { | |
DaleCurtis
2016/11/14 17:58:20
Do we need this function? It seems like we should
wolenetz
2016/11/15 02:15:35
We record stats including coded size (perhaps we d
| |
26 DCHECK(context); | |
27 | |
28 // Open a temporary decode, to try to get coded size if codec is supported. | |
29 AVCodec* codec = avcodec_find_decoder(context->codec_id); | |
DaleCurtis
2016/11/14 17:58:21
Doesn't this need to be released?
wolenetz
2016/11/15 02:15:35
No. Rather, the caller's management of |context|'s
| |
30 if (codec && avcodec_open2(context, codec, NULL) >= 0 && | |
31 context->coded_width > 0 && context->coded_height > 0) { | |
32 return gfx::Size(context->coded_width, context->coded_height); | |
33 } | |
34 | |
35 // Otherwise, either ffmpeg does not support decode of the codec or opening | |
36 // the decoder failed. Default to the width/height. If a decoder other than | |
37 // ffmpeg is used, it will need to figure out the correct coded size. | |
38 return gfx::Size(context->width, context->height); | |
39 } | |
40 | |
25 EncryptionScheme GetEncryptionScheme(const AVStream* stream) { | 41 EncryptionScheme GetEncryptionScheme(const AVStream* stream) { |
26 AVDictionaryEntry* key = | 42 AVDictionaryEntry* key = |
27 av_dict_get(stream->metadata, "enc_key_id", nullptr, 0); | 43 av_dict_get(stream->metadata, "enc_key_id", nullptr, 0); |
28 return key ? AesCtrEncryptionScheme() : Unencrypted(); | 44 return key ? AesCtrEncryptionScheme() : Unencrypted(); |
29 } | 45 } |
30 | 46 |
31 } // namespace | 47 } // namespace |
32 | 48 |
33 // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are | 49 // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are |
34 // padded. Check here to ensure FFmpeg only receives data padded to its | 50 // padded. Check here to ensure FFmpeg only receives data padded to its |
(...skipping 365 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
400 DCHECK_EQ(av_get_bytes_per_sample(codec_context->sample_fmt) * 8, | 416 DCHECK_EQ(av_get_bytes_per_sample(codec_context->sample_fmt) * 8, |
401 config->bits_per_channel()); | 417 config->bits_per_channel()); |
402 break; | 418 break; |
403 } | 419 } |
404 | 420 |
405 return true; | 421 return true; |
406 } | 422 } |
407 | 423 |
408 bool AVStreamToAudioDecoderConfig(const AVStream* stream, | 424 bool AVStreamToAudioDecoderConfig(const AVStream* stream, |
409 AudioDecoderConfig* config) { | 425 AudioDecoderConfig* config) { |
426 std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context( | |
DaleCurtis
2016/11/14 17:58:20
Seems this could be extracted to a helper function
wolenetz
2016/11/15 02:15:35
SGTM. I was beginning to think similar :)
Done.
| |
427 avcodec_alloc_context3(NULL)); | |
428 if (!codec_context || | |
429 avcodec_parameters_to_context(codec_context.get(), stream->codecpar) < | |
430 0) { | |
431 return false; | |
432 } | |
433 | |
410 return AVCodecContextToAudioDecoderConfig( | 434 return AVCodecContextToAudioDecoderConfig( |
411 stream->codec, GetEncryptionScheme(stream), config); | 435 codec_context.get(), GetEncryptionScheme(stream), config); |
412 } | 436 } |
413 | 437 |
414 void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config, | 438 void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config, |
415 AVCodecContext* codec_context) { | 439 AVCodecContext* codec_context) { |
416 codec_context->codec_type = AVMEDIA_TYPE_AUDIO; | 440 codec_context->codec_type = AVMEDIA_TYPE_AUDIO; |
417 codec_context->codec_id = AudioCodecToCodecID(config.codec(), | 441 codec_context->codec_id = AudioCodecToCodecID(config.codec(), |
418 config.sample_format()); | 442 config.sample_format()); |
419 codec_context->sample_fmt = SampleFormatToAVSampleFormat( | 443 codec_context->sample_fmt = SampleFormatToAVSampleFormat( |
420 config.sample_format()); | 444 config.sample_format()); |
421 | 445 |
(...skipping 12 matching lines...) Expand all Loading... | |
434 av_malloc(config.extra_data().size() + FF_INPUT_BUFFER_PADDING_SIZE)); | 458 av_malloc(config.extra_data().size() + FF_INPUT_BUFFER_PADDING_SIZE)); |
435 memcpy(codec_context->extradata, &config.extra_data()[0], | 459 memcpy(codec_context->extradata, &config.extra_data()[0], |
436 config.extra_data().size()); | 460 config.extra_data().size()); |
437 memset(codec_context->extradata + config.extra_data().size(), '\0', | 461 memset(codec_context->extradata + config.extra_data().size(), '\0', |
438 FF_INPUT_BUFFER_PADDING_SIZE); | 462 FF_INPUT_BUFFER_PADDING_SIZE); |
439 } | 463 } |
440 } | 464 } |
441 | 465 |
442 bool AVStreamToVideoDecoderConfig(const AVStream* stream, | 466 bool AVStreamToVideoDecoderConfig(const AVStream* stream, |
443 VideoDecoderConfig* config) { | 467 VideoDecoderConfig* config) { |
444 gfx::Size coded_size(stream->codec->coded_width, stream->codec->coded_height); | 468 std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context( |
469 avcodec_alloc_context3(NULL)); | |
470 if (!codec_context || | |
471 avcodec_parameters_to_context(codec_context.get(), stream->codecpar) < | |
472 0) { | |
473 return false; | |
474 } | |
475 | |
476 // This call will attempt to open a decoder to further populate | |
477 // |codec_context|. | |
478 gfx::Size coded_size(GetCodedSize(codec_context.get())); | |
445 | 479 |
446 // TODO(vrk): This assumes decoded frame data starts at (0, 0), which is true | 480 // TODO(vrk): This assumes decoded frame data starts at (0, 0), which is true |
447 // for now, but may not always be true forever. Fix this in the future. | 481 // for now, but may not always be true forever. Fix this in the future. |
448 gfx::Rect visible_rect(stream->codec->width, stream->codec->height); | 482 gfx::Rect visible_rect(codec_context->width, codec_context->height); |
449 | 483 |
450 AVRational aspect_ratio = { 1, 1 }; | 484 AVRational aspect_ratio = { 1, 1 }; |
451 if (stream->sample_aspect_ratio.num) | 485 if (stream->sample_aspect_ratio.num) |
452 aspect_ratio = stream->sample_aspect_ratio; | 486 aspect_ratio = stream->sample_aspect_ratio; |
453 else if (stream->codec->sample_aspect_ratio.num) | 487 else if (codec_context->sample_aspect_ratio.num) |
454 aspect_ratio = stream->codec->sample_aspect_ratio; | 488 aspect_ratio = codec_context->sample_aspect_ratio; |
455 | 489 |
456 VideoCodec codec = CodecIDToVideoCodec(stream->codec->codec_id); | 490 VideoCodec codec = CodecIDToVideoCodec(codec_context->codec_id); |
457 | 491 |
458 VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; | 492 VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; |
459 if (codec == kCodecVP8) | 493 if (codec == kCodecVP8) |
460 profile = VP8PROFILE_ANY; | 494 profile = VP8PROFILE_ANY; |
461 else if (codec == kCodecVP9) | 495 else if (codec == kCodecVP9) |
462 // TODO(servolk): Find a way to obtain actual VP9 profile from FFmpeg. | 496 // TODO(servolk): Find a way to obtain actual VP9 profile from FFmpeg. |
463 // crbug.com/592074 | 497 // crbug.com/592074 |
464 profile = VP9PROFILE_PROFILE0; | 498 profile = VP9PROFILE_PROFILE0; |
465 else | 499 else |
466 profile = ProfileIDToVideoCodecProfile(stream->codec->profile); | 500 profile = ProfileIDToVideoCodecProfile(codec_context->profile); |
467 | 501 |
468 // Without the FFmpeg h264 decoder, AVFormat is unable to get the profile, so | 502 // Without the FFmpeg h264 decoder, AVFormat is unable to get the profile, so |
469 // default to baseline and let the VDA fail later if it doesn't support the | 503 // default to baseline and let the VDA fail later if it doesn't support the |
470 // real profile. This is alright because if the FFmpeg h264 decoder isn't | 504 // real profile. This is alright because if the FFmpeg h264 decoder isn't |
471 // enabled, there is no fallback if the VDA fails. | 505 // enabled, there is no fallback if the VDA fails. |
472 #if defined(DISABLE_FFMPEG_VIDEO_DECODERS) | 506 #if defined(DISABLE_FFMPEG_VIDEO_DECODERS) |
473 if (codec == kCodecH264) | 507 if (codec == kCodecH264) |
474 profile = H264PROFILE_BASELINE; | 508 profile = H264PROFILE_BASELINE; |
475 #endif | 509 #endif |
476 | 510 |
477 gfx::Size natural_size = GetNaturalSize( | 511 gfx::Size natural_size = GetNaturalSize( |
478 visible_rect.size(), aspect_ratio.num, aspect_ratio.den); | 512 visible_rect.size(), aspect_ratio.num, aspect_ratio.den); |
479 | 513 |
480 VideoPixelFormat format = | 514 VideoPixelFormat format = |
481 AVPixelFormatToVideoPixelFormat(stream->codec->pix_fmt); | 515 AVPixelFormatToVideoPixelFormat(codec_context->pix_fmt); |
482 // The format and coded size may be unknown if FFmpeg is compiled without | 516 // The format and coded size may be unknown if FFmpeg is compiled without |
483 // video decoders. | 517 // video decoders. |
484 #if defined(DISABLE_FFMPEG_VIDEO_DECODERS) | 518 #if defined(DISABLE_FFMPEG_VIDEO_DECODERS) |
485 if (format == PIXEL_FORMAT_UNKNOWN) | 519 if (format == PIXEL_FORMAT_UNKNOWN) |
486 format = PIXEL_FORMAT_YV12; | 520 format = PIXEL_FORMAT_YV12; |
487 if (coded_size == gfx::Size(0, 0)) | 521 if (coded_size == gfx::Size(0, 0)) |
488 coded_size = visible_rect.size(); | 522 coded_size = visible_rect.size(); |
489 #endif | 523 #endif |
490 | 524 |
491 if (codec == kCodecVP9) { | 525 if (codec == kCodecVP9) { |
492 // TODO(tomfinegan): libavcodec doesn't know about VP9. | 526 // TODO(tomfinegan): libavcodec doesn't know about VP9. |
493 format = PIXEL_FORMAT_YV12; | 527 format = PIXEL_FORMAT_YV12; |
494 coded_size = visible_rect.size(); | 528 coded_size = visible_rect.size(); |
495 } | 529 } |
496 | 530 |
497 // Pad out |coded_size| for subsampled YUV formats. | 531 // Pad out |coded_size| for subsampled YUV formats. |
498 if (format != PIXEL_FORMAT_YV24) { | 532 if (format != PIXEL_FORMAT_YV24) { |
499 coded_size.set_width((coded_size.width() + 1) / 2 * 2); | 533 coded_size.set_width((coded_size.width() + 1) / 2 * 2); |
500 if (format != PIXEL_FORMAT_YV16) | 534 if (format != PIXEL_FORMAT_YV16) |
501 coded_size.set_height((coded_size.height() + 1) / 2 * 2); | 535 coded_size.set_height((coded_size.height() + 1) / 2 * 2); |
502 } | 536 } |
503 | 537 |
504 AVDictionaryEntry* webm_alpha = | 538 AVDictionaryEntry* webm_alpha = |
505 av_dict_get(stream->metadata, "alpha_mode", nullptr, 0); | 539 av_dict_get(stream->metadata, "alpha_mode", nullptr, 0); |
506 if (webm_alpha && !strcmp(webm_alpha->value, "1")) { | 540 if (webm_alpha && !strcmp(webm_alpha->value, "1")) { |
507 format = PIXEL_FORMAT_YV12A; | 541 format = PIXEL_FORMAT_YV12A; |
508 } | 542 } |
509 | 543 |
510 // Prefer the color space found by libavcodec if available. | 544 // Prefer the color space found by libavcodec if available. |
511 ColorSpace color_space = AVColorSpaceToColorSpace(stream->codec->colorspace, | 545 ColorSpace color_space = AVColorSpaceToColorSpace(codec_context->colorspace, |
512 stream->codec->color_range); | 546 codec_context->color_range); |
513 if (color_space == COLOR_SPACE_UNSPECIFIED) { | 547 if (color_space == COLOR_SPACE_UNSPECIFIED) { |
514 // Otherwise, assume that SD video is usually Rec.601, and HD is usually | 548 // Otherwise, assume that SD video is usually Rec.601, and HD is usually |
515 // Rec.709. | 549 // Rec.709. |
516 color_space = (natural_size.height() < 720) ? COLOR_SPACE_SD_REC601 | 550 color_space = (natural_size.height() < 720) ? COLOR_SPACE_SD_REC601 |
517 : COLOR_SPACE_HD_REC709; | 551 : COLOR_SPACE_HD_REC709; |
518 } | 552 } |
519 | 553 |
520 // AVStream occasionally has invalid extra data. See http://crbug.com/517163 | 554 // AVCodecContext occasionally has invalid extra data. See |
521 if ((stream->codec->extradata_size == 0) != | 555 // http://crbug.com/517163 |
522 (stream->codec->extradata == nullptr)) { | 556 if (codec_context->extradata != nullptr && |
523 LOG(ERROR) << __func__ | 557 codec_context->extradata_size == 0) { |
524 << (stream->codec->extradata == nullptr ? " NULL" : " Non-Null") | 558 LOG(ERROR) << __func__ << " Non-Null extra data cannot have size of 0."; |
525 << " extra data cannot have size of " | |
526 << stream->codec->extradata_size << "."; | |
527 return false; | 559 return false; |
528 } | 560 } |
561 CHECK_EQ(codec_context->extradata == nullptr, | |
562 codec_context->extradata_size == 0); | |
529 | 563 |
530 std::vector<uint8_t> extra_data; | 564 std::vector<uint8_t> extra_data; |
531 if (stream->codec->extradata_size > 0) { | 565 if (codec_context->extradata_size > 0) { |
532 extra_data.assign(stream->codec->extradata, | 566 extra_data.assign(codec_context->extradata, |
533 stream->codec->extradata + stream->codec->extradata_size); | 567 codec_context->extradata + codec_context->extradata_size); |
534 } | 568 } |
535 config->Initialize(codec, profile, format, color_space, coded_size, | 569 config->Initialize(codec, profile, format, color_space, coded_size, |
536 visible_rect, natural_size, extra_data, | 570 visible_rect, natural_size, extra_data, |
537 GetEncryptionScheme(stream)); | 571 GetEncryptionScheme(stream)); |
538 return true; | 572 return true; |
539 } | 573 } |
540 | 574 |
541 void VideoDecoderConfigToAVCodecContext( | 575 void VideoDecoderConfigToAVCodecContext( |
542 const VideoDecoderConfig& config, | 576 const VideoDecoderConfig& config, |
543 AVCodecContext* codec_context) { | 577 AVCodecContext* codec_context) { |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
727 default: | 761 default: |
728 DVLOG(1) << "Unknown AVColorSpace: " << color_space; | 762 DVLOG(1) << "Unknown AVColorSpace: " << color_space; |
729 } | 763 } |
730 return COLOR_SPACE_UNSPECIFIED; | 764 return COLOR_SPACE_UNSPECIFIED; |
731 } | 765 } |
732 | 766 |
733 bool FFmpegUTCDateToTime(const char* date_utc, base::Time* out) { | 767 bool FFmpegUTCDateToTime(const char* date_utc, base::Time* out) { |
734 DCHECK(date_utc); | 768 DCHECK(date_utc); |
735 DCHECK(out); | 769 DCHECK(out); |
736 | 770 |
771 // Note, base::Time::FromUTCString(date_utc, out) would work for parsing a | |
DaleCurtis
2016/11/14 17:58:20
Is it really worth having our own implementation?
wolenetz
2016/11/15 02:15:35
Yeah, I tried that first, and found the leniency f
| |
772 // valid FFmpegUTC strictly standardized |date_utc|, but it is more flexible | |
773 // to allowing missing pieces and having different formats. This method | |
774 // enforces stricter format expectation of FFmpeg. | |
737 std::vector<base::StringPiece> fields = base::SplitStringPiece( | 775 std::vector<base::StringPiece> fields = base::SplitStringPiece( |
738 date_utc, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | 776 date_utc, "T", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
739 if (fields.size() != 2) | 777 if (fields.size() != 2) |
740 return false; | 778 return false; |
741 | 779 |
742 std::vector<base::StringPiece> date_fields = base::SplitStringPiece( | 780 std::vector<base::StringPiece> date_fields = base::SplitStringPiece( |
743 fields[0], "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | 781 fields[0], "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
744 if (date_fields.size() != 3) | 782 if (date_fields.size() != 3) |
745 return false; | 783 return false; |
746 | 784 |
747 // TODO(acolwell): Update this parsing code when FFmpeg returns sub-second | |
748 // information. | |
749 std::vector<base::StringPiece> time_fields = base::SplitStringPiece( | 785 std::vector<base::StringPiece> time_fields = base::SplitStringPiece( |
750 fields[1], ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | 786 fields[1], ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
751 if (time_fields.size() != 3) | 787 if (time_fields.size() != 2) |
788 return false; | |
789 | |
790 std::vector<base::StringPiece> hms_fields = base::SplitStringPiece( | |
791 time_fields[0], ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | |
792 if (hms_fields.size() != 3) | |
793 return false; | |
794 | |
795 std::vector<base::StringPiece> subsecond_fields = base::SplitStringPiece( | |
796 time_fields[1], "Z", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
797 if (subsecond_fields.size() != 2 || subsecond_fields[0].length() != 6 || | |
798 !subsecond_fields[1].empty()) { | |
799 return false; | |
800 } | |
801 | |
802 int microseconds; | |
803 if (!base::StringToInt(subsecond_fields[0], µseconds)) | |
752 return false; | 804 return false; |
753 | 805 |
754 base::Time::Exploded exploded; | 806 base::Time::Exploded exploded; |
755 exploded.millisecond = 0; | |
756 // This field cannot be uninitialized. Unless not modified, make it 0 here | 807 // This field cannot be uninitialized. Unless not modified, make it 0 here |
757 // then. | 808 // then. |
758 exploded.day_of_week = 0; | 809 exploded.day_of_week = 0; |
810 | |
811 exploded.millisecond = microseconds / base::Time::kMicrosecondsPerMillisecond; | |
759 if (base::StringToInt(date_fields[0], &exploded.year) && | 812 if (base::StringToInt(date_fields[0], &exploded.year) && |
760 base::StringToInt(date_fields[1], &exploded.month) && | 813 base::StringToInt(date_fields[1], &exploded.month) && |
761 base::StringToInt(date_fields[2], &exploded.day_of_month) && | 814 base::StringToInt(date_fields[2], &exploded.day_of_month) && |
762 base::StringToInt(time_fields[0], &exploded.hour) && | 815 base::StringToInt(hms_fields[0], &exploded.hour) && |
763 base::StringToInt(time_fields[1], &exploded.minute) && | 816 base::StringToInt(hms_fields[1], &exploded.minute) && |
764 base::StringToInt(time_fields[2], &exploded.second)) { | 817 base::StringToInt(hms_fields[2], &exploded.second)) { |
765 if (base::Time::FromUTCExploded(exploded, out)) | 818 if (base::Time::FromUTCExploded(exploded, out)) { |
819 *out += base::TimeDelta::FromMicroseconds( | |
820 microseconds % base::Time::kMicrosecondsPerMillisecond); | |
766 return true; | 821 return true; |
822 } | |
767 } | 823 } |
768 | 824 |
769 return false; | 825 return false; |
770 } | 826 } |
771 | 827 |
772 int32_t HashCodecName(const char* codec_name) { | 828 int32_t HashCodecName(const char* codec_name) { |
773 // Use the first 32-bits from the SHA1 hash as the identifier. | 829 // Use the first 32-bits from the SHA1 hash as the identifier. |
774 int32_t hash; | 830 int32_t hash; |
775 memcpy(&hash, base::SHA1HashString(codec_name).substr(0, 4).c_str(), 4); | 831 memcpy(&hash, base::SHA1HashString(codec_name).substr(0, 4).c_str(), 4); |
776 return hash; | 832 return hash; |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
828 TEST_COLORSPACE(RESERVED); | 884 TEST_COLORSPACE(RESERVED); |
829 TEST_COLORSPACE(FCC); | 885 TEST_COLORSPACE(FCC); |
830 TEST_COLORSPACE(BT470BG); | 886 TEST_COLORSPACE(BT470BG); |
831 TEST_COLORSPACE(SMPTE170M); | 887 TEST_COLORSPACE(SMPTE170M); |
832 TEST_COLORSPACE(SMPTE240M); | 888 TEST_COLORSPACE(SMPTE240M); |
833 TEST_COLORSPACE(YCOCG); | 889 TEST_COLORSPACE(YCOCG); |
834 TEST_COLORSPACE(BT2020_NCL); | 890 TEST_COLORSPACE(BT2020_NCL); |
835 TEST_COLORSPACE(BT2020_CL); | 891 TEST_COLORSPACE(BT2020_CL); |
836 | 892 |
837 } // namespace media | 893 } // namespace media |
OLD | NEW |