| 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 "content/common/gpu/media/dxva_video_decode_accelerator.h" | 5 #include "content/common/gpu/media/dxva_video_decode_accelerator.h" |
| 6 | 6 |
| 7 #if !defined(OS_WIN) | 7 #if !defined(OS_WIN) |
| 8 #error This file should only be built on Windows. | 8 #error This file should only be built on Windows. |
| 9 #endif // !defined(OS_WIN) | 9 #endif // !defined(OS_WIN) |
| 10 | 10 |
| (...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 423 } | 423 } |
| 424 | 424 |
| 425 DXVAVideoDecodeAccelerator::DXVAVideoDecodeAccelerator( | 425 DXVAVideoDecodeAccelerator::DXVAVideoDecodeAccelerator( |
| 426 const base::Callback<bool(void)>& make_context_current) | 426 const base::Callback<bool(void)>& make_context_current) |
| 427 : client_(NULL), | 427 : client_(NULL), |
| 428 dev_manager_reset_token_(0), | 428 dev_manager_reset_token_(0), |
| 429 egl_config_(NULL), | 429 egl_config_(NULL), |
| 430 state_(kUninitialized), | 430 state_(kUninitialized), |
| 431 pictures_requested_(false), | 431 pictures_requested_(false), |
| 432 inputs_before_decode_(0), | 432 inputs_before_decode_(0), |
| 433 make_context_current_(make_context_current) { | 433 make_context_current_(make_context_current), |
| 434 weak_this_factory_(this) { |
| 434 memset(&input_stream_info_, 0, sizeof(input_stream_info_)); | 435 memset(&input_stream_info_, 0, sizeof(input_stream_info_)); |
| 435 memset(&output_stream_info_, 0, sizeof(output_stream_info_)); | 436 memset(&output_stream_info_, 0, sizeof(output_stream_info_)); |
| 436 } | 437 } |
| 437 | 438 |
| 438 DXVAVideoDecodeAccelerator::~DXVAVideoDecodeAccelerator() { | 439 DXVAVideoDecodeAccelerator::~DXVAVideoDecodeAccelerator() { |
| 439 client_ = NULL; | 440 client_ = NULL; |
| 440 } | 441 } |
| 441 | 442 |
| 442 bool DXVAVideoDecodeAccelerator::Initialize(media::VideoCodecProfile profile, | 443 bool DXVAVideoDecodeAccelerator::Initialize(media::VideoCodecProfile profile, |
| 443 Client* client) { | 444 Client* client) { |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 491 SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0), | 492 SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0), |
| 492 "Send MFT_MESSAGE_NOTIFY_BEGIN_STREAMING notification failed", | 493 "Send MFT_MESSAGE_NOTIFY_BEGIN_STREAMING notification failed", |
| 493 PLATFORM_FAILURE, false); | 494 PLATFORM_FAILURE, false); |
| 494 | 495 |
| 495 RETURN_AND_NOTIFY_ON_FAILURE( | 496 RETURN_AND_NOTIFY_ON_FAILURE( |
| 496 SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0), | 497 SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0), |
| 497 "Send MFT_MESSAGE_NOTIFY_START_OF_STREAM notification failed", | 498 "Send MFT_MESSAGE_NOTIFY_START_OF_STREAM notification failed", |
| 498 PLATFORM_FAILURE, false); | 499 PLATFORM_FAILURE, false); |
| 499 | 500 |
| 500 state_ = kNormal; | 501 state_ = kNormal; |
| 501 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | |
| 502 &DXVAVideoDecodeAccelerator::NotifyInitializeDone, | |
| 503 base::AsWeakPtr(this))); | |
| 504 return true; | 502 return true; |
| 505 } | 503 } |
| 506 | 504 |
| 507 void DXVAVideoDecodeAccelerator::Decode( | 505 void DXVAVideoDecodeAccelerator::Decode( |
| 508 const media::BitstreamBuffer& bitstream_buffer) { | 506 const media::BitstreamBuffer& bitstream_buffer) { |
| 509 DCHECK(CalledOnValidThread()); | 507 DCHECK(CalledOnValidThread()); |
| 510 | 508 |
| 511 RETURN_AND_NOTIFY_ON_FAILURE((state_ == kNormal || state_ == kStopped || | 509 RETURN_AND_NOTIFY_ON_FAILURE((state_ == kNormal || state_ == kStopped || |
| 512 state_ == kFlushing), | 510 state_ == kFlushing), |
| 513 "Invalid state: " << state_, ILLEGAL_STATE,); | 511 "Invalid state: " << state_, ILLEGAL_STATE,); |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 603 | 601 |
| 604 state_ = kResetting; | 602 state_ = kResetting; |
| 605 | 603 |
| 606 pending_output_samples_.clear(); | 604 pending_output_samples_.clear(); |
| 607 | 605 |
| 608 NotifyInputBuffersDropped(); | 606 NotifyInputBuffersDropped(); |
| 609 | 607 |
| 610 RETURN_AND_NOTIFY_ON_FAILURE(SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0), | 608 RETURN_AND_NOTIFY_ON_FAILURE(SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0), |
| 611 "Reset: Failed to send message.", PLATFORM_FAILURE,); | 609 "Reset: Failed to send message.", PLATFORM_FAILURE,); |
| 612 | 610 |
| 613 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 611 base::MessageLoop::current()->PostTask( |
| 614 &DXVAVideoDecodeAccelerator::NotifyResetDone, base::AsWeakPtr(this))); | 612 FROM_HERE, |
| 613 base::Bind(&DXVAVideoDecodeAccelerator::NotifyResetDone, |
| 614 weak_this_factory_.GetWeakPtr())); |
| 615 | 615 |
| 616 state_ = DXVAVideoDecodeAccelerator::kNormal; | 616 state_ = DXVAVideoDecodeAccelerator::kNormal; |
| 617 } | 617 } |
| 618 | 618 |
| 619 void DXVAVideoDecodeAccelerator::Destroy() { | 619 void DXVAVideoDecodeAccelerator::Destroy() { |
| 620 DCHECK(CalledOnValidThread()); | 620 DCHECK(CalledOnValidThread()); |
| 621 Invalidate(); | 621 Invalidate(); |
| 622 delete this; | 622 delete this; |
| 623 } | 623 } |
| 624 | 624 |
| (...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 877 } | 877 } |
| 878 | 878 |
| 879 // We only read the surface description, which contains its width/height when | 879 // We only read the surface description, which contains its width/height when |
| 880 // we need the picture buffers from the client. Once we have those, then they | 880 // we need the picture buffers from the client. Once we have those, then they |
| 881 // are reused. | 881 // are reused. |
| 882 D3DSURFACE_DESC surface_desc; | 882 D3DSURFACE_DESC surface_desc; |
| 883 hr = surface->GetDesc(&surface_desc); | 883 hr = surface->GetDesc(&surface_desc); |
| 884 RETURN_ON_HR_FAILURE(hr, "Failed to get surface description", false); | 884 RETURN_ON_HR_FAILURE(hr, "Failed to get surface description", false); |
| 885 | 885 |
| 886 // Go ahead and request picture buffers. | 886 // Go ahead and request picture buffers. |
| 887 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 887 base::MessageLoop::current()->PostTask( |
| 888 &DXVAVideoDecodeAccelerator::RequestPictureBuffers, | 888 FROM_HERE, |
| 889 base::AsWeakPtr(this), surface_desc.Width, surface_desc.Height)); | 889 base::Bind(&DXVAVideoDecodeAccelerator::RequestPictureBuffers, |
| 890 weak_this_factory_.GetWeakPtr(), |
| 891 surface_desc.Width, |
| 892 surface_desc.Height)); |
| 890 | 893 |
| 891 pictures_requested_ = true; | 894 pictures_requested_ = true; |
| 892 return true; | 895 return true; |
| 893 } | 896 } |
| 894 | 897 |
| 895 void DXVAVideoDecodeAccelerator::ProcessPendingSamples() { | 898 void DXVAVideoDecodeAccelerator::ProcessPendingSamples() { |
| 896 RETURN_AND_NOTIFY_ON_FAILURE(make_context_current_.Run(), | 899 RETURN_AND_NOTIFY_ON_FAILURE(make_context_current_.Run(), |
| 897 "Failed to make context current", PLATFORM_FAILURE,); | 900 "Failed to make context current", PLATFORM_FAILURE,); |
| 898 | 901 |
| 899 OutputBuffers::iterator index; | 902 OutputBuffers::iterator index; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 931 return; | 934 return; |
| 932 } | 935 } |
| 933 | 936 |
| 934 RETURN_AND_NOTIFY_ON_FAILURE( | 937 RETURN_AND_NOTIFY_ON_FAILURE( |
| 935 index->second->CopyOutputSampleDataToPictureBuffer(*this, surface), | 938 index->second->CopyOutputSampleDataToPictureBuffer(*this, surface), |
| 936 "Failed to copy output sample", | 939 "Failed to copy output sample", |
| 937 PLATFORM_FAILURE, ); | 940 PLATFORM_FAILURE, ); |
| 938 | 941 |
| 939 media::Picture output_picture(index->second->id(), | 942 media::Picture output_picture(index->second->id(), |
| 940 sample_info.input_buffer_id); | 943 sample_info.input_buffer_id); |
| 941 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 944 base::MessageLoop::current()->PostTask( |
| 942 &DXVAVideoDecodeAccelerator::NotifyPictureReady, | 945 FROM_HERE, |
| 943 base::AsWeakPtr(this), output_picture)); | 946 base::Bind(&DXVAVideoDecodeAccelerator::NotifyPictureReady, |
| 947 weak_this_factory_.GetWeakPtr(), |
| 948 output_picture)); |
| 944 | 949 |
| 945 index->second->set_available(false); | 950 index->second->set_available(false); |
| 946 pending_output_samples_.pop_front(); | 951 pending_output_samples_.pop_front(); |
| 947 } | 952 } |
| 948 } | 953 } |
| 949 | 954 |
| 950 if (!pending_input_buffers_.empty() && pending_output_samples_.empty()) { | 955 if (!pending_input_buffers_.empty() && pending_output_samples_.empty()) { |
| 951 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 956 base::MessageLoop::current()->PostTask( |
| 952 &DXVAVideoDecodeAccelerator::DecodePendingInputBuffers, | 957 FROM_HERE, |
| 953 base::AsWeakPtr(this))); | 958 base::Bind(&DXVAVideoDecodeAccelerator::DecodePendingInputBuffers, |
| 959 weak_this_factory_.GetWeakPtr())); |
| 954 } | 960 } |
| 955 } | 961 } |
| 956 | 962 |
| 957 void DXVAVideoDecodeAccelerator::StopOnError( | 963 void DXVAVideoDecodeAccelerator::StopOnError( |
| 958 media::VideoDecodeAccelerator::Error error) { | 964 media::VideoDecodeAccelerator::Error error) { |
| 959 DCHECK(CalledOnValidThread()); | 965 DCHECK(CalledOnValidThread()); |
| 960 | 966 |
| 961 if (client_) | 967 if (client_) |
| 962 client_->NotifyError(error); | 968 client_->NotifyError(error); |
| 963 client_ = NULL; | 969 client_ = NULL; |
| 964 | 970 |
| 965 if (state_ != kUninitialized) { | 971 if (state_ != kUninitialized) { |
| 966 Invalidate(); | 972 Invalidate(); |
| 967 } | 973 } |
| 968 } | 974 } |
| 969 | 975 |
| 970 void DXVAVideoDecodeAccelerator::Invalidate() { | 976 void DXVAVideoDecodeAccelerator::Invalidate() { |
| 971 if (state_ == kUninitialized) | 977 if (state_ == kUninitialized) |
| 972 return; | 978 return; |
| 979 weak_this_factory_.InvalidateWeakPtrs(); |
| 973 output_picture_buffers_.clear(); | 980 output_picture_buffers_.clear(); |
| 974 pending_output_samples_.clear(); | 981 pending_output_samples_.clear(); |
| 975 pending_input_buffers_.clear(); | 982 pending_input_buffers_.clear(); |
| 976 decoder_.Release(); | 983 decoder_.Release(); |
| 977 MFShutdown(); | 984 MFShutdown(); |
| 978 state_ = kUninitialized; | 985 state_ = kUninitialized; |
| 979 } | 986 } |
| 980 | 987 |
| 981 void DXVAVideoDecodeAccelerator::NotifyInitializeDone() { | |
| 982 if (client_) | |
| 983 client_->NotifyInitializeDone(); | |
| 984 } | |
| 985 | |
| 986 void DXVAVideoDecodeAccelerator::NotifyInputBufferRead(int input_buffer_id) { | 988 void DXVAVideoDecodeAccelerator::NotifyInputBufferRead(int input_buffer_id) { |
| 987 if (client_) | 989 if (client_) |
| 988 client_->NotifyEndOfBitstreamBuffer(input_buffer_id); | 990 client_->NotifyEndOfBitstreamBuffer(input_buffer_id); |
| 989 } | 991 } |
| 990 | 992 |
| 991 void DXVAVideoDecodeAccelerator::NotifyFlushDone() { | 993 void DXVAVideoDecodeAccelerator::NotifyFlushDone() { |
| 992 if (client_) | 994 if (client_) |
| 993 client_->NotifyFlushDone(); | 995 client_->NotifyFlushDone(); |
| 994 } | 996 } |
| 995 | 997 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1050 // MF_E_TRANSFORM_NEED_MORE_INPUT. | 1052 // MF_E_TRANSFORM_NEED_MORE_INPUT. |
| 1051 // The MFT decoder can buffer upto 30 frames worth of input before returning | 1053 // The MFT decoder can buffer upto 30 frames worth of input before returning |
| 1052 // an output frame. This loop here attempts to retrieve as many output frames | 1054 // an output frame. This loop here attempts to retrieve as many output frames |
| 1053 // as possible from the buffered set. | 1055 // as possible from the buffered set. |
| 1054 while (state_ != kStopped) { | 1056 while (state_ != kStopped) { |
| 1055 DoDecode(); | 1057 DoDecode(); |
| 1056 if (!pending_output_samples_.empty()) | 1058 if (!pending_output_samples_.empty()) |
| 1057 return; | 1059 return; |
| 1058 } | 1060 } |
| 1059 | 1061 |
| 1060 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 1062 base::MessageLoop::current()->PostTask( |
| 1061 &DXVAVideoDecodeAccelerator::NotifyFlushDone, base::AsWeakPtr(this))); | 1063 FROM_HERE, |
| 1064 base::Bind(&DXVAVideoDecodeAccelerator::NotifyFlushDone, |
| 1065 weak_this_factory_.GetWeakPtr())); |
| 1062 | 1066 |
| 1063 state_ = kNormal; | 1067 state_ = kNormal; |
| 1064 } | 1068 } |
| 1065 | 1069 |
| 1066 void DXVAVideoDecodeAccelerator::DecodeInternal( | 1070 void DXVAVideoDecodeAccelerator::DecodeInternal( |
| 1067 const base::win::ScopedComPtr<IMFSample>& sample) { | 1071 const base::win::ScopedComPtr<IMFSample>& sample) { |
| 1068 DCHECK(CalledOnValidThread()); | 1072 DCHECK(CalledOnValidThread()); |
| 1069 | 1073 |
| 1070 if (state_ == kUninitialized) | 1074 if (state_ == kUninitialized) |
| 1071 return; | 1075 return; |
| (...skipping 29 matching lines...) Expand all Loading... |
| 1101 // 2. If we don't have any output samples we post the | 1105 // 2. If we don't have any output samples we post the |
| 1102 // DecodePendingInputBuffers task to process the pending input samples. | 1106 // DecodePendingInputBuffers task to process the pending input samples. |
| 1103 // If we have an output sample then the above task is posted when the | 1107 // If we have an output sample then the above task is posted when the |
| 1104 // output samples are sent to the client. | 1108 // output samples are sent to the client. |
| 1105 // This is because we only support 1 pending output sample at any | 1109 // This is because we only support 1 pending output sample at any |
| 1106 // given time due to the limitation with the Microsoft media foundation | 1110 // given time due to the limitation with the Microsoft media foundation |
| 1107 // decoder where it recycles the output Decoder surfaces. | 1111 // decoder where it recycles the output Decoder surfaces. |
| 1108 if (hr == MF_E_NOTACCEPTING) { | 1112 if (hr == MF_E_NOTACCEPTING) { |
| 1109 pending_input_buffers_.push_back(sample); | 1113 pending_input_buffers_.push_back(sample); |
| 1110 if (pending_output_samples_.empty()) { | 1114 if (pending_output_samples_.empty()) { |
| 1111 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 1115 base::MessageLoop::current()->PostTask( |
| 1112 &DXVAVideoDecodeAccelerator::DecodePendingInputBuffers, | 1116 FROM_HERE, |
| 1113 base::AsWeakPtr(this))); | 1117 base::Bind(&DXVAVideoDecodeAccelerator::DecodePendingInputBuffers, |
| 1118 weak_this_factory_.GetWeakPtr())); |
| 1114 } | 1119 } |
| 1115 return; | 1120 return; |
| 1116 } | 1121 } |
| 1117 } | 1122 } |
| 1118 RETURN_AND_NOTIFY_ON_HR_FAILURE(hr, "Failed to process input sample", | 1123 RETURN_AND_NOTIFY_ON_HR_FAILURE(hr, "Failed to process input sample", |
| 1119 PLATFORM_FAILURE,); | 1124 PLATFORM_FAILURE,); |
| 1120 | 1125 |
| 1121 DoDecode(); | 1126 DoDecode(); |
| 1122 | 1127 |
| 1123 RETURN_AND_NOTIFY_ON_FAILURE((state_ == kStopped || state_ == kNormal), | 1128 RETURN_AND_NOTIFY_ON_FAILURE((state_ == kStopped || state_ == kNormal), |
| 1124 "Failed to process output. Unexpected decoder state: " << state_, | 1129 "Failed to process output. Unexpected decoder state: " << state_, |
| 1125 ILLEGAL_STATE,); | 1130 ILLEGAL_STATE,); |
| 1126 | 1131 |
| 1127 LONGLONG input_buffer_id = 0; | 1132 LONGLONG input_buffer_id = 0; |
| 1128 RETURN_ON_HR_FAILURE(sample->GetSampleTime(&input_buffer_id), | 1133 RETURN_ON_HR_FAILURE(sample->GetSampleTime(&input_buffer_id), |
| 1129 "Failed to get input buffer id associated with sample",); | 1134 "Failed to get input buffer id associated with sample",); |
| 1130 // The Microsoft Media foundation decoder internally buffers up to 30 frames | 1135 // The Microsoft Media foundation decoder internally buffers up to 30 frames |
| 1131 // before returning a decoded frame. We need to inform the client that this | 1136 // before returning a decoded frame. We need to inform the client that this |
| 1132 // input buffer is processed as it may stop sending us further input. | 1137 // input buffer is processed as it may stop sending us further input. |
| 1133 // Note: This may break clients which expect every input buffer to be | 1138 // Note: This may break clients which expect every input buffer to be |
| 1134 // associated with a decoded output buffer. | 1139 // associated with a decoded output buffer. |
| 1135 // TODO(ananta) | 1140 // TODO(ananta) |
| 1136 // Do some more investigation into whether it is possible to get the MFT | 1141 // Do some more investigation into whether it is possible to get the MFT |
| 1137 // decoder to emit an output packet for every input packet. | 1142 // decoder to emit an output packet for every input packet. |
| 1138 // http://code.google.com/p/chromium/issues/detail?id=108121 | 1143 // http://code.google.com/p/chromium/issues/detail?id=108121 |
| 1139 // http://code.google.com/p/chromium/issues/detail?id=150925 | 1144 // http://code.google.com/p/chromium/issues/detail?id=150925 |
| 1140 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 1145 base::MessageLoop::current()->PostTask( |
| 1141 &DXVAVideoDecodeAccelerator::NotifyInputBufferRead, | 1146 FROM_HERE, |
| 1142 base::AsWeakPtr(this), input_buffer_id)); | 1147 base::Bind(&DXVAVideoDecodeAccelerator::NotifyInputBufferRead, |
| 1148 weak_this_factory_.GetWeakPtr(), |
| 1149 input_buffer_id)); |
| 1143 } | 1150 } |
| 1144 | 1151 |
| 1145 void DXVAVideoDecodeAccelerator::HandleResolutionChanged(int width, | 1152 void DXVAVideoDecodeAccelerator::HandleResolutionChanged(int width, |
| 1146 int height) { | 1153 int height) { |
| 1147 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 1154 base::MessageLoop::current()->PostTask( |
| 1148 &DXVAVideoDecodeAccelerator::DismissStaleBuffers, | 1155 FROM_HERE, |
| 1149 base::AsWeakPtr(this), output_picture_buffers_)); | 1156 base::Bind(&DXVAVideoDecodeAccelerator::DismissStaleBuffers, |
| 1157 weak_this_factory_.GetWeakPtr(), |
| 1158 output_picture_buffers_)); |
| 1150 | 1159 |
| 1151 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | 1160 base::MessageLoop::current()->PostTask( |
| 1152 &DXVAVideoDecodeAccelerator::RequestPictureBuffers, | 1161 FROM_HERE, |
| 1153 base::AsWeakPtr(this), width, height)); | 1162 base::Bind(&DXVAVideoDecodeAccelerator::RequestPictureBuffers, |
| 1163 weak_this_factory_.GetWeakPtr(), |
| 1164 width, |
| 1165 height)); |
| 1154 | 1166 |
| 1155 output_picture_buffers_.clear(); | 1167 output_picture_buffers_.clear(); |
| 1156 } | 1168 } |
| 1157 | 1169 |
| 1158 void DXVAVideoDecodeAccelerator::DismissStaleBuffers( | 1170 void DXVAVideoDecodeAccelerator::DismissStaleBuffers( |
| 1159 const OutputBuffers& picture_buffers) { | 1171 const OutputBuffers& picture_buffers) { |
| 1160 OutputBuffers::const_iterator index; | 1172 OutputBuffers::const_iterator index; |
| 1161 | 1173 |
| 1162 for (index = picture_buffers.begin(); | 1174 for (index = picture_buffers.begin(); |
| 1163 index != picture_buffers.end(); | 1175 index != picture_buffers.end(); |
| 1164 ++index) { | 1176 ++index) { |
| 1165 DVLOG(1) << "Dismissing picture id: " << index->second->id(); | 1177 DVLOG(1) << "Dismissing picture id: " << index->second->id(); |
| 1166 client_->DismissPictureBuffer(index->second->id()); | 1178 client_->DismissPictureBuffer(index->second->id()); |
| 1167 } | 1179 } |
| 1168 } | 1180 } |
| 1169 | 1181 |
| 1170 } // namespace content | 1182 } // namespace content |
| OLD | NEW |