| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 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/capture/video/android/video_capture_device_android.h" | 5 #include "media/capture/video/android/video_capture_device_android.h" |
| 6 | 6 |
| 7 #include <stdint.h> | 7 #include <stdint.h> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/android/jni_android.h" | 10 #include "base/android/jni_android.h" |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 // only used for USB model identifiers, so this implementation just indicates | 36 // only used for USB model identifiers, so this implementation just indicates |
| 37 // an unknown device model. | 37 // an unknown device model. |
| 38 return ""; | 38 return ""; |
| 39 } | 39 } |
| 40 | 40 |
| 41 VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(const Name& device_name) | 41 VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(const Name& device_name) |
| 42 : state_(kIdle), got_first_frame_(false), device_name_(device_name) { | 42 : state_(kIdle), got_first_frame_(false), device_name_(device_name) { |
| 43 } | 43 } |
| 44 | 44 |
| 45 VideoCaptureDeviceAndroid::~VideoCaptureDeviceAndroid() { | 45 VideoCaptureDeviceAndroid::~VideoCaptureDeviceAndroid() { |
| 46 DCHECK(thread_checker_.CalledOnValidThread()); |
| 46 StopAndDeAllocate(); | 47 StopAndDeAllocate(); |
| 47 } | 48 } |
| 48 | 49 |
| 49 bool VideoCaptureDeviceAndroid::Init() { | 50 bool VideoCaptureDeviceAndroid::Init() { |
| 50 int id; | 51 int id; |
| 51 if (!base::StringToInt(device_name_.id(), &id)) | 52 if (!base::StringToInt(device_name_.id(), &id)) |
| 52 return false; | 53 return false; |
| 53 | 54 |
| 54 j_capture_.Reset(VideoCaptureDeviceFactoryAndroid::createVideoCaptureAndroid( | 55 j_capture_.Reset(VideoCaptureDeviceFactoryAndroid::createVideoCaptureAndroid( |
| 55 id, reinterpret_cast<intptr_t>(this))); | 56 id, reinterpret_cast<intptr_t>(this))); |
| 56 return true; | 57 return true; |
| 57 } | 58 } |
| 58 | 59 |
| 59 void VideoCaptureDeviceAndroid::AllocateAndStart( | 60 void VideoCaptureDeviceAndroid::AllocateAndStart( |
| 60 const VideoCaptureParams& params, | 61 const VideoCaptureParams& params, |
| 61 std::unique_ptr<Client> client) { | 62 std::unique_ptr<Client> client) { |
| 62 DVLOG(1) << __FUNCTION__; | 63 DCHECK(thread_checker_.CalledOnValidThread()); |
| 63 { | 64 { |
| 64 base::AutoLock lock(lock_); | 65 base::AutoLock lock(lock_); |
| 65 if (state_ != kIdle) | 66 if (state_ != kIdle) |
| 66 return; | 67 return; |
| 67 client_ = std::move(client); | 68 client_ = std::move(client); |
| 68 got_first_frame_ = false; | 69 got_first_frame_ = false; |
| 69 } | 70 } |
| 70 | 71 |
| 71 JNIEnv* env = AttachCurrentThread(); | 72 JNIEnv* env = AttachCurrentThread(); |
| 72 | 73 |
| 73 jboolean ret = Java_VideoCapture_allocate( | 74 jboolean ret = Java_VideoCapture_allocate( |
| 74 env, j_capture_.obj(), params.requested_format.frame_size.width(), | 75 env, j_capture_.obj(), params.requested_format.frame_size.width(), |
| 75 params.requested_format.frame_size.height(), | 76 params.requested_format.frame_size.height(), |
| 76 params.requested_format.frame_rate); | 77 params.requested_format.frame_rate); |
| 77 if (!ret) { | 78 if (!ret) { |
| 78 SetErrorState(FROM_HERE, "failed to allocate"); | 79 SetErrorState(FROM_HERE, "failed to allocate"); |
| 79 return; | 80 return; |
| 80 } | 81 } |
| 81 | 82 |
| 82 // Store current width and height. | |
| 83 capture_format_.frame_size.SetSize( | 83 capture_format_.frame_size.SetSize( |
| 84 Java_VideoCapture_queryWidth(env, j_capture_.obj()), | 84 Java_VideoCapture_queryWidth(env, j_capture_.obj()), |
| 85 Java_VideoCapture_queryHeight(env, j_capture_.obj())); | 85 Java_VideoCapture_queryHeight(env, j_capture_.obj())); |
| 86 capture_format_.frame_rate = | 86 capture_format_.frame_rate = |
| 87 Java_VideoCapture_queryFrameRate(env, j_capture_.obj()); | 87 Java_VideoCapture_queryFrameRate(env, j_capture_.obj()); |
| 88 capture_format_.pixel_format = GetColorspace(); | 88 capture_format_.pixel_format = GetColorspace(); |
| 89 DCHECK_NE(capture_format_.pixel_format, media::PIXEL_FORMAT_UNKNOWN); | 89 DCHECK_NE(capture_format_.pixel_format, media::PIXEL_FORMAT_UNKNOWN); |
| 90 CHECK(capture_format_.frame_size.GetArea() > 0); | 90 CHECK(capture_format_.frame_size.GetArea() > 0); |
| 91 CHECK(!(capture_format_.frame_size.width() % 2)); | 91 CHECK(!(capture_format_.frame_size.width() % 2)); |
| 92 CHECK(!(capture_format_.frame_size.height() % 2)); | 92 CHECK(!(capture_format_.frame_size.height() % 2)); |
| 93 | 93 |
| 94 if (capture_format_.frame_rate > 0) { | 94 if (capture_format_.frame_rate > 0) { |
| 95 frame_interval_ = base::TimeDelta::FromMicroseconds( | 95 frame_interval_ = base::TimeDelta::FromMicroseconds( |
| 96 (base::Time::kMicrosecondsPerSecond + capture_format_.frame_rate - 1) / | 96 (base::Time::kMicrosecondsPerSecond + capture_format_.frame_rate - 1) / |
| 97 capture_format_.frame_rate); | 97 capture_format_.frame_rate); |
| 98 } | 98 } |
| 99 | 99 |
| 100 DVLOG(1) << "VideoCaptureDeviceAndroid::Allocate: queried frame_size=" | 100 DVLOG(1) << __FUNCTION__ << " requested (" |
| 101 << capture_format_.frame_size.ToString() | 101 << capture_format_.frame_size.ToString() << ")@ " |
| 102 << ", frame_rate=" << capture_format_.frame_rate; | 102 << capture_format_.frame_rate << "fps"; |
| 103 | 103 |
| 104 ret = Java_VideoCapture_startCapture(env, j_capture_.obj()); | 104 ret = Java_VideoCapture_startCapture(env, j_capture_.obj()); |
| 105 if (!ret) { | 105 if (!ret) { |
| 106 SetErrorState(FROM_HERE, "failed to start capture"); | 106 SetErrorState(FROM_HERE, "failed to start capture"); |
| 107 return; | 107 return; |
| 108 } | 108 } |
| 109 | 109 |
| 110 { | 110 { |
| 111 base::AutoLock lock(lock_); | 111 base::AutoLock lock(lock_); |
| 112 state_ = kCapturing; | 112 state_ = kCapturing; |
| 113 } | 113 } |
| 114 } | 114 } |
| 115 | 115 |
| 116 void VideoCaptureDeviceAndroid::StopAndDeAllocate() { | 116 void VideoCaptureDeviceAndroid::StopAndDeAllocate() { |
| 117 DVLOG(1) << __FUNCTION__; | 117 DCHECK(thread_checker_.CalledOnValidThread()); |
| 118 { | 118 { |
| 119 base::AutoLock lock(lock_); | 119 base::AutoLock lock(lock_); |
| 120 if (state_ != kCapturing && state_ != kError) | 120 if (state_ != kCapturing && state_ != kError) |
| 121 return; | 121 return; |
| 122 } | 122 } |
| 123 | 123 |
| 124 JNIEnv* env = AttachCurrentThread(); | 124 JNIEnv* env = AttachCurrentThread(); |
| 125 | 125 |
| 126 jboolean ret = Java_VideoCapture_stopCapture(env, j_capture_.obj()); | 126 const jboolean ret = Java_VideoCapture_stopCapture(env, j_capture_.obj()); |
| 127 if (!ret) { | 127 if (!ret) { |
| 128 SetErrorState(FROM_HERE, "failed to stop capture"); | 128 SetErrorState(FROM_HERE, "failed to stop capture"); |
| 129 return; | 129 return; |
| 130 } | 130 } |
| 131 | 131 |
| 132 { | 132 { |
| 133 base::AutoLock lock(lock_); | 133 base::AutoLock lock(lock_); |
| 134 state_ = kIdle; | 134 state_ = kIdle; |
| 135 client_.reset(); | 135 client_.reset(); |
| 136 } | 136 } |
| 137 | 137 |
| 138 Java_VideoCapture_deallocate(env, j_capture_.obj()); | 138 Java_VideoCapture_deallocate(env, j_capture_.obj()); |
| 139 } | 139 } |
| 140 | 140 |
| 141 void VideoCaptureDeviceAndroid::TakePhoto(TakePhotoCallback callback) { | 141 void VideoCaptureDeviceAndroid::TakePhoto(TakePhotoCallback callback) { |
| 142 DVLOG(1) << __FUNCTION__; | 142 DCHECK(thread_checker_.CalledOnValidThread()); |
| 143 { | 143 { |
| 144 base::AutoLock lock(lock_); | 144 base::AutoLock lock(lock_); |
| 145 if (state_ != kCapturing) | 145 if (state_ != kCapturing) |
| 146 return; | 146 return; |
| 147 } | 147 } |
| 148 | 148 |
| 149 JNIEnv* env = AttachCurrentThread(); | 149 JNIEnv* env = AttachCurrentThread(); |
| 150 | 150 |
| 151 // Make copy on the heap so we can pass the pointer through JNI. | 151 // Make copy on the heap so we can pass the pointer through JNI. |
| 152 std::unique_ptr<TakePhotoCallback> heap_callback( | 152 std::unique_ptr<TakePhotoCallback> heap_callback( |
| 153 new TakePhotoCallback(std::move(callback))); | 153 new TakePhotoCallback(std::move(callback))); |
| 154 const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get()); | 154 const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get()); |
| 155 if (!Java_VideoCapture_takePhoto(env, j_capture_.obj(), callback_id, | 155 if (!Java_VideoCapture_takePhoto(env, j_capture_.obj(), callback_id, |
| 156 next_photo_resolution_.width(), | 156 next_photo_resolution_.width(), |
| 157 next_photo_resolution_.height())) | 157 next_photo_resolution_.height())) |
| 158 return; | 158 return; |
| 159 | 159 |
| 160 { | 160 { |
| 161 base::AutoLock lock(photo_callbacks_lock_); | 161 base::AutoLock lock(photo_callbacks_lock_); |
| 162 photo_callbacks_.push_back(std::move(heap_callback)); | 162 photo_callbacks_.push_back(std::move(heap_callback)); |
| 163 } | 163 } |
| 164 } | 164 } |
| 165 | 165 |
| 166 void VideoCaptureDeviceAndroid::GetPhotoCapabilities( | 166 void VideoCaptureDeviceAndroid::GetPhotoCapabilities( |
| 167 GetPhotoCapabilitiesCallback callback) { | 167 GetPhotoCapabilitiesCallback callback) { |
| 168 DCHECK(thread_checker_.CalledOnValidThread()); |
| 168 JNIEnv* env = AttachCurrentThread(); | 169 JNIEnv* env = AttachCurrentThread(); |
| 169 | 170 |
| 170 PhotoCapabilities caps( | 171 PhotoCapabilities caps( |
| 171 Java_VideoCapture_getPhotoCapabilities(env, j_capture_.obj())); | 172 Java_VideoCapture_getPhotoCapabilities(env, j_capture_.obj())); |
| 172 | 173 |
| 173 // TODO(mcasas): Manual member copying sucks, consider adding typemapping from | 174 // TODO(mcasas): Manual member copying sucks, consider adding typemapping from |
| 174 // PhotoCapabilities to mojom::PhotoCapabilitiesPtr, https://crbug.com/622002. | 175 // PhotoCapabilities to mojom::PhotoCapabilitiesPtr, https://crbug.com/622002. |
| 175 mojom::PhotoCapabilitiesPtr photo_capabilities = | 176 mojom::PhotoCapabilitiesPtr photo_capabilities = |
| 176 mojom::PhotoCapabilities::New(); | 177 mojom::PhotoCapabilities::New(); |
| 177 photo_capabilities->iso = mojom::Range::New(); | 178 photo_capabilities->iso = mojom::Range::New(); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 192 photo_capabilities->zoom->min = caps.getMinZoom(); | 193 photo_capabilities->zoom->min = caps.getMinZoom(); |
| 193 photo_capabilities->focus_mode = caps.getAutoFocusInUse() | 194 photo_capabilities->focus_mode = caps.getAutoFocusInUse() |
| 194 ? mojom::FocusMode::AUTO | 195 ? mojom::FocusMode::AUTO |
| 195 : mojom::FocusMode::MANUAL; | 196 : mojom::FocusMode::MANUAL; |
| 196 callback.Run(std::move(photo_capabilities)); | 197 callback.Run(std::move(photo_capabilities)); |
| 197 } | 198 } |
| 198 | 199 |
| 199 void VideoCaptureDeviceAndroid::SetPhotoOptions( | 200 void VideoCaptureDeviceAndroid::SetPhotoOptions( |
| 200 mojom::PhotoSettingsPtr settings, | 201 mojom::PhotoSettingsPtr settings, |
| 201 SetPhotoOptionsCallback callback) { | 202 SetPhotoOptionsCallback callback) { |
| 203 DCHECK(thread_checker_.CalledOnValidThread()); |
| 202 JNIEnv* env = AttachCurrentThread(); | 204 JNIEnv* env = AttachCurrentThread(); |
| 203 // |width| and/or |height| are kept for the next TakePhoto()s. | 205 // |width| and/or |height| are kept for the next TakePhoto()s. |
| 204 if (settings->has_width || settings->has_height) | 206 if (settings->has_width || settings->has_height) |
| 205 next_photo_resolution_.SetSize(0, 0); | 207 next_photo_resolution_.SetSize(0, 0); |
| 206 if (settings->has_width) { | 208 if (settings->has_width) { |
| 207 next_photo_resolution_.set_width( | 209 next_photo_resolution_.set_width( |
| 208 base::saturated_cast<int>(settings->width)); | 210 base::saturated_cast<int>(settings->width)); |
| 209 } | 211 } |
| 210 if (settings->has_height) { | 212 if (settings->has_height) { |
| 211 next_photo_resolution_.set_height( | 213 next_photo_resolution_.set_height( |
| 212 base::saturated_cast<int>(settings->height)); | 214 base::saturated_cast<int>(settings->height)); |
| 213 } | 215 } |
| 214 | 216 |
| 215 if (settings->has_zoom) | 217 if (settings->has_zoom) |
| 216 Java_VideoCapture_setZoom(env, j_capture_.obj(), settings->zoom); | 218 Java_VideoCapture_setZoom(env, j_capture_.obj(), settings->zoom); |
| 217 callback.Run(true); | 219 callback.Run(true); |
| 218 } | 220 } |
| 219 | 221 |
| 220 void VideoCaptureDeviceAndroid::OnFrameAvailable( | 222 void VideoCaptureDeviceAndroid::OnFrameAvailable( |
| 221 JNIEnv* env, | 223 JNIEnv* env, |
| 222 const JavaParamRef<jobject>& obj, | 224 const JavaParamRef<jobject>& obj, |
| 223 const JavaParamRef<jbyteArray>& data, | 225 const JavaParamRef<jbyteArray>& data, |
| 224 jint length, | 226 jint length, |
| 225 jint rotation) { | 227 jint rotation) { |
| 226 DVLOG(3) << __FUNCTION__ << " length =" << length; | 228 { |
| 227 | 229 base::AutoLock lock(lock_); |
| 228 base::AutoLock lock(lock_); | 230 if (state_ != kCapturing || !client_) |
| 229 if (state_ != kCapturing || !client_.get()) | 231 return; |
| 230 return; | 232 } |
| 231 | 233 |
| 232 jbyte* buffer = env->GetByteArrayElements(data, NULL); | 234 jbyte* buffer = env->GetByteArrayElements(data, NULL); |
| 233 if (!buffer) { | 235 if (!buffer) { |
| 234 LOG(ERROR) << "VideoCaptureDeviceAndroid::OnFrameAvailable: " | 236 LOG(ERROR) << "VideoCaptureDeviceAndroid::OnFrameAvailable: " |
| 235 "failed to GetByteArrayElements"; | 237 "failed to GetByteArrayElements"; |
| 236 return; | 238 return; |
| 237 } | 239 } |
| 238 | 240 |
| 239 const base::TimeTicks current_time = base::TimeTicks::Now(); | 241 const base::TimeTicks current_time = base::TimeTicks::Now(); |
| 240 if (!got_first_frame_) { | 242 if (!got_first_frame_) { |
| 241 // Set aside one frame allowance for fluctuation. | 243 // Set aside one frame allowance for fluctuation. |
| 242 expected_next_frame_time_ = current_time - frame_interval_; | 244 expected_next_frame_time_ = current_time - frame_interval_; |
| 243 first_ref_time_ = current_time; | 245 first_ref_time_ = current_time; |
| 244 got_first_frame_ = true; | 246 got_first_frame_ = true; |
| 245 } | 247 } |
| 246 | 248 |
| 247 // Deliver the frame when it doesn't arrive too early. | 249 // Deliver the frame when it doesn't arrive too early. |
| 248 if (expected_next_frame_time_ <= current_time) { | 250 if (expected_next_frame_time_ <= current_time) { |
| 249 expected_next_frame_time_ += frame_interval_; | 251 expected_next_frame_time_ += frame_interval_; |
| 250 | 252 |
| 251 // TODO(qiangchen): Investigate how to get raw timestamp for Android, | 253 // TODO(qiangchen): Investigate how to get raw timestamp for Android, |
| 252 // rather than using reference time to calculate timestamp. | 254 // rather than using reference time to calculate timestamp. |
| 255 base::AutoLock lock(lock_); |
| 256 if (!client_) |
| 257 return; |
| 253 client_->OnIncomingCapturedData(reinterpret_cast<uint8_t*>(buffer), length, | 258 client_->OnIncomingCapturedData(reinterpret_cast<uint8_t*>(buffer), length, |
| 254 capture_format_, rotation, current_time, | 259 capture_format_, rotation, current_time, |
| 255 current_time - first_ref_time_); | 260 current_time - first_ref_time_); |
| 256 } | 261 } |
| 257 | 262 |
| 258 env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); | 263 env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); |
| 259 } | 264 } |
| 260 | 265 |
| 261 void VideoCaptureDeviceAndroid::OnI420FrameAvailable(JNIEnv* env, | 266 void VideoCaptureDeviceAndroid::OnI420FrameAvailable(JNIEnv* env, |
| 262 jobject obj, | 267 jobject obj, |
| 263 jobject y_buffer, | 268 jobject y_buffer, |
| 264 jint y_stride, | 269 jint y_stride, |
| 265 jobject u_buffer, | 270 jobject u_buffer, |
| 266 jobject v_buffer, | 271 jobject v_buffer, |
| 267 jint uv_row_stride, | 272 jint uv_row_stride, |
| 268 jint uv_pixel_stride, | 273 jint uv_pixel_stride, |
| 269 jint width, | 274 jint width, |
| 270 jint height, | 275 jint height, |
| 271 jint rotation) { | 276 jint rotation) { |
| 277 { |
| 278 base::AutoLock lock(lock_); |
| 279 if (state_ != kCapturing || !client_) |
| 280 return; |
| 281 } |
| 282 |
| 272 const base::TimeTicks current_time = base::TimeTicks::Now(); | 283 const base::TimeTicks current_time = base::TimeTicks::Now(); |
| 273 if (!got_first_frame_) { | 284 if (!got_first_frame_) { |
| 274 // Set aside one frame allowance for fluctuation. | 285 // Set aside one frame allowance for fluctuation. |
| 275 expected_next_frame_time_ = current_time - frame_interval_; | 286 expected_next_frame_time_ = current_time - frame_interval_; |
| 276 first_ref_time_ = current_time; | 287 first_ref_time_ = current_time; |
| 277 got_first_frame_ = true; | 288 got_first_frame_ = true; |
| 278 } | 289 } |
| 279 | 290 |
| 280 uint8_t* const y_src = | 291 uint8_t* const y_src = |
| 281 reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(y_buffer)); | 292 reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(y_buffer)); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 297 buffer.get() + y_plane_length, width / 2, | 308 buffer.get() + y_plane_length, width / 2, |
| 298 buffer.get() + y_plane_length + uv_plane_length, | 309 buffer.get() + y_plane_length + uv_plane_length, |
| 299 width / 2, width, height); | 310 width / 2, width, height); |
| 300 | 311 |
| 301 // Deliver the frame when it doesn't arrive too early. | 312 // Deliver the frame when it doesn't arrive too early. |
| 302 if (expected_next_frame_time_ <= current_time) { | 313 if (expected_next_frame_time_ <= current_time) { |
| 303 expected_next_frame_time_ += frame_interval_; | 314 expected_next_frame_time_ += frame_interval_; |
| 304 | 315 |
| 305 // TODO(qiangchen): Investigate how to get raw timestamp for Android, | 316 // TODO(qiangchen): Investigate how to get raw timestamp for Android, |
| 306 // rather than using reference time to calculate timestamp. | 317 // rather than using reference time to calculate timestamp. |
| 318 base::AutoLock lock(lock_); |
| 319 if (!client_) |
| 320 return; |
| 307 client_->OnIncomingCapturedData(buffer.get(), buffer_length, | 321 client_->OnIncomingCapturedData(buffer.get(), buffer_length, |
| 308 capture_format_, rotation, current_time, | 322 capture_format_, rotation, current_time, |
| 309 current_time - first_ref_time_); | 323 current_time - first_ref_time_); |
| 310 } | 324 } |
| 311 } | 325 } |
| 312 | 326 |
| 313 void VideoCaptureDeviceAndroid::OnError(JNIEnv* env, | 327 void VideoCaptureDeviceAndroid::OnError(JNIEnv* env, |
| 314 const JavaParamRef<jobject>& obj, | 328 const JavaParamRef<jobject>& obj, |
| 315 const JavaParamRef<jstring>& message) { | 329 const JavaParamRef<jstring>& message) { |
| 316 SetErrorState(FROM_HERE, | 330 SetErrorState(FROM_HERE, |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 return media::PIXEL_FORMAT_UNKNOWN; | 377 return media::PIXEL_FORMAT_UNKNOWN; |
| 364 } | 378 } |
| 365 } | 379 } |
| 366 | 380 |
| 367 void VideoCaptureDeviceAndroid::SetErrorState( | 381 void VideoCaptureDeviceAndroid::SetErrorState( |
| 368 const tracked_objects::Location& from_here, | 382 const tracked_objects::Location& from_here, |
| 369 const std::string& reason) { | 383 const std::string& reason) { |
| 370 { | 384 { |
| 371 base::AutoLock lock(lock_); | 385 base::AutoLock lock(lock_); |
| 372 state_ = kError; | 386 state_ = kError; |
| 387 if (!client_) |
| 388 return; |
| 389 client_->OnError(from_here, reason); |
| 373 } | 390 } |
| 374 client_->OnError(from_here, reason); | |
| 375 } | 391 } |
| 376 | 392 |
| 377 } // namespace media | 393 } // namespace media |
| OLD | NEW |