| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 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/gpu/ipc/client/gpu_video_encode_accelerator_host.h" | 5 #include "media/gpu/ipc/client/gpu_video_encode_accelerator_host.h" |
| 6 | 6 |
| 7 #include "base/location.h" | 7 #include "base/location.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/threading/thread_task_runner_handle.h" | 9 #include "base/threading/thread_task_runner_handle.h" |
| 10 #include "gpu/ipc/client/gpu_channel_host.h" | 10 #include "gpu/ipc/client/gpu_channel_host.h" |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 impl_(impl), | 23 impl_(impl), |
| 24 next_frame_id_(0), | 24 next_frame_id_(0), |
| 25 media_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 25 media_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 26 weak_this_factory_(this) { | 26 weak_this_factory_(this) { |
| 27 DCHECK(channel_); | 27 DCHECK(channel_); |
| 28 DCHECK(impl_); | 28 DCHECK(impl_); |
| 29 impl_->AddDeletionObserver(this); | 29 impl_->AddDeletionObserver(this); |
| 30 } | 30 } |
| 31 | 31 |
| 32 GpuVideoEncodeAcceleratorHost::~GpuVideoEncodeAcceleratorHost() { | 32 GpuVideoEncodeAcceleratorHost::~GpuVideoEncodeAcceleratorHost() { |
| 33 DCHECK(CalledOnValidThread()); | 33 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 34 if (channel_ && encoder_route_id_ != MSG_ROUTING_NONE) | 34 if (channel_ && encoder_route_id_ != MSG_ROUTING_NONE) |
| 35 channel_->RemoveRoute(encoder_route_id_); | 35 channel_->RemoveRoute(encoder_route_id_); |
| 36 | 36 |
| 37 base::AutoLock lock(impl_lock_); | 37 base::AutoLock lock(impl_lock_); |
| 38 if (impl_) | 38 if (impl_) |
| 39 impl_->RemoveDeletionObserver(this); | 39 impl_->RemoveDeletionObserver(this); |
| 40 } | 40 } |
| 41 | 41 |
| 42 bool GpuVideoEncodeAcceleratorHost::OnMessageReceived( | 42 bool GpuVideoEncodeAcceleratorHost::OnMessageReceived( |
| 43 const IPC::Message& message) { | 43 const IPC::Message& message) { |
| 44 bool handled = true; | 44 bool handled = true; |
| 45 IPC_BEGIN_MESSAGE_MAP(GpuVideoEncodeAcceleratorHost, message) | 45 IPC_BEGIN_MESSAGE_MAP(GpuVideoEncodeAcceleratorHost, message) |
| 46 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_RequireBitstreamBuffers, | 46 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_RequireBitstreamBuffers, |
| 47 OnRequireBitstreamBuffers) | 47 OnRequireBitstreamBuffers) |
| 48 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_NotifyInputDone, | 48 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_NotifyInputDone, |
| 49 OnNotifyInputDone) | 49 OnNotifyInputDone) |
| 50 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_BitstreamBufferReady, | 50 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_BitstreamBufferReady, |
| 51 OnBitstreamBufferReady) | 51 OnBitstreamBufferReady) |
| 52 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_NotifyError, | 52 IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderHostMsg_NotifyError, |
| 53 OnNotifyError) | 53 OnNotifyError) |
| 54 IPC_MESSAGE_UNHANDLED(handled = false) | 54 IPC_MESSAGE_UNHANDLED(handled = false) |
| 55 IPC_END_MESSAGE_MAP() | 55 IPC_END_MESSAGE_MAP() |
| 56 DCHECK(handled); | 56 DCHECK(handled); |
| 57 // See OnNotifyError for why |this| mustn't be used after OnNotifyError might | 57 // See OnNotifyError for why |this| mustn't be used after OnNotifyError might |
| 58 // have been called above. | 58 // have been called above. |
| 59 return handled; | 59 return handled; |
| 60 } | 60 } |
| 61 | 61 |
| 62 void GpuVideoEncodeAcceleratorHost::OnChannelError() { | 62 void GpuVideoEncodeAcceleratorHost::OnChannelError() { |
| 63 DCHECK(CalledOnValidThread()); | 63 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 64 if (channel_) { | 64 if (channel_) { |
| 65 if (encoder_route_id_ != MSG_ROUTING_NONE) | 65 if (encoder_route_id_ != MSG_ROUTING_NONE) |
| 66 channel_->RemoveRoute(encoder_route_id_); | 66 channel_->RemoveRoute(encoder_route_id_); |
| 67 channel_ = nullptr; | 67 channel_ = nullptr; |
| 68 } | 68 } |
| 69 PostNotifyError(FROM_HERE, kPlatformFailureError, "OnChannelError()"); | 69 PostNotifyError(FROM_HERE, kPlatformFailureError, "OnChannelError()"); |
| 70 } | 70 } |
| 71 | 71 |
| 72 VideoEncodeAccelerator::SupportedProfiles | 72 VideoEncodeAccelerator::SupportedProfiles |
| 73 GpuVideoEncodeAcceleratorHost::GetSupportedProfiles() { | 73 GpuVideoEncodeAcceleratorHost::GetSupportedProfiles() { |
| 74 DCHECK(CalledOnValidThread()); | 74 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 75 if (!channel_) | 75 if (!channel_) |
| 76 return VideoEncodeAccelerator::SupportedProfiles(); | 76 return VideoEncodeAccelerator::SupportedProfiles(); |
| 77 return GpuVideoAcceleratorUtil::ConvertGpuToMediaEncodeProfiles( | 77 return GpuVideoAcceleratorUtil::ConvertGpuToMediaEncodeProfiles( |
| 78 channel_->gpu_info().video_encode_accelerator_supported_profiles); | 78 channel_->gpu_info().video_encode_accelerator_supported_profiles); |
| 79 } | 79 } |
| 80 | 80 |
| 81 bool GpuVideoEncodeAcceleratorHost::Initialize( | 81 bool GpuVideoEncodeAcceleratorHost::Initialize( |
| 82 VideoPixelFormat input_format, | 82 VideoPixelFormat input_format, |
| 83 const gfx::Size& input_visible_size, | 83 const gfx::Size& input_visible_size, |
| 84 VideoCodecProfile output_profile, | 84 VideoCodecProfile output_profile, |
| 85 uint32_t initial_bitrate, | 85 uint32_t initial_bitrate, |
| 86 Client* client) { | 86 Client* client) { |
| 87 DCHECK(CalledOnValidThread()); | 87 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 88 client_ = client; | 88 client_ = client; |
| 89 | 89 |
| 90 base::AutoLock lock(impl_lock_); | 90 base::AutoLock lock(impl_lock_); |
| 91 if (!impl_) { | 91 if (!impl_) { |
| 92 DLOG(ERROR) << "impl_ destroyed"; | 92 DLOG(ERROR) << "impl_ destroyed"; |
| 93 return false; | 93 return false; |
| 94 } | 94 } |
| 95 | 95 |
| 96 int32_t route_id = channel_->GenerateRouteID(); | 96 int32_t route_id = channel_->GenerateRouteID(); |
| 97 channel_->AddRoute(route_id, weak_this_factory_.GetWeakPtr()); | 97 channel_->AddRoute(route_id, weak_this_factory_.GetWeakPtr()); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 110 channel_->RemoveRoute(route_id); | 110 channel_->RemoveRoute(route_id); |
| 111 return false; | 111 return false; |
| 112 } | 112 } |
| 113 encoder_route_id_ = route_id; | 113 encoder_route_id_ = route_id; |
| 114 return true; | 114 return true; |
| 115 } | 115 } |
| 116 | 116 |
| 117 void GpuVideoEncodeAcceleratorHost::Encode( | 117 void GpuVideoEncodeAcceleratorHost::Encode( |
| 118 const scoped_refptr<VideoFrame>& frame, | 118 const scoped_refptr<VideoFrame>& frame, |
| 119 bool force_keyframe) { | 119 bool force_keyframe) { |
| 120 DCHECK(CalledOnValidThread()); | 120 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 121 DCHECK_EQ(PIXEL_FORMAT_I420, frame->format()); | 121 DCHECK_EQ(PIXEL_FORMAT_I420, frame->format()); |
| 122 DCHECK_EQ(VideoFrame::STORAGE_SHMEM, frame->storage_type()); | 122 DCHECK_EQ(VideoFrame::STORAGE_SHMEM, frame->storage_type()); |
| 123 if (!channel_) | 123 if (!channel_) |
| 124 return; | 124 return; |
| 125 | 125 |
| 126 switch (frame->storage_type()) { | 126 switch (frame->storage_type()) { |
| 127 case VideoFrame::STORAGE_SHMEM: | 127 case VideoFrame::STORAGE_SHMEM: |
| 128 EncodeSharedMemoryFrame(frame, force_keyframe); | 128 EncodeSharedMemoryFrame(frame, force_keyframe); |
| 129 break; | 129 break; |
| 130 default: | 130 default: |
| 131 PostNotifyError(FROM_HERE, kPlatformFailureError, | 131 PostNotifyError(FROM_HERE, kPlatformFailureError, |
| 132 "Encode(): cannot encode frame with invalid handles"); | 132 "Encode(): cannot encode frame with invalid handles"); |
| 133 return; | 133 return; |
| 134 } | 134 } |
| 135 | 135 |
| 136 frame_map_[next_frame_id_] = frame; | 136 frame_map_[next_frame_id_] = frame; |
| 137 | 137 |
| 138 // Mask against 30 bits, to avoid (undefined) wraparound on signed integer. | 138 // Mask against 30 bits, to avoid (undefined) wraparound on signed integer. |
| 139 next_frame_id_ = (next_frame_id_ + 1) & 0x3FFFFFFF; | 139 next_frame_id_ = (next_frame_id_ + 1) & 0x3FFFFFFF; |
| 140 } | 140 } |
| 141 | 141 |
| 142 void GpuVideoEncodeAcceleratorHost::UseOutputBitstreamBuffer( | 142 void GpuVideoEncodeAcceleratorHost::UseOutputBitstreamBuffer( |
| 143 const BitstreamBuffer& buffer) { | 143 const BitstreamBuffer& buffer) { |
| 144 DCHECK(CalledOnValidThread()); | 144 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 145 if (!channel_) | 145 if (!channel_) |
| 146 return; | 146 return; |
| 147 | 147 |
| 148 base::SharedMemoryHandle handle = | 148 base::SharedMemoryHandle handle = |
| 149 channel_->ShareToGpuProcess(buffer.handle()); | 149 channel_->ShareToGpuProcess(buffer.handle()); |
| 150 if (!base::SharedMemory::IsHandleValid(handle)) { | 150 if (!base::SharedMemory::IsHandleValid(handle)) { |
| 151 PostNotifyError( | 151 PostNotifyError( |
| 152 FROM_HERE, kPlatformFailureError, | 152 FROM_HERE, kPlatformFailureError, |
| 153 base::StringPrintf("UseOutputBitstreamBuffer(): failed to duplicate " | 153 base::StringPrintf("UseOutputBitstreamBuffer(): failed to duplicate " |
| 154 "buffer handle for GPU process: buffer.id()=%d", | 154 "buffer handle for GPU process: buffer.id()=%d", |
| 155 buffer.id())); | 155 buffer.id())); |
| 156 return; | 156 return; |
| 157 } | 157 } |
| 158 Send(new AcceleratedVideoEncoderMsg_UseOutputBitstreamBuffer( | 158 Send(new AcceleratedVideoEncoderMsg_UseOutputBitstreamBuffer( |
| 159 encoder_route_id_, buffer.id(), handle, buffer.size())); | 159 encoder_route_id_, buffer.id(), handle, buffer.size())); |
| 160 } | 160 } |
| 161 | 161 |
| 162 void GpuVideoEncodeAcceleratorHost::RequestEncodingParametersChange( | 162 void GpuVideoEncodeAcceleratorHost::RequestEncodingParametersChange( |
| 163 uint32_t bitrate, | 163 uint32_t bitrate, |
| 164 uint32_t framerate) { | 164 uint32_t framerate) { |
| 165 DCHECK(CalledOnValidThread()); | 165 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 166 if (!channel_) | 166 if (!channel_) |
| 167 return; | 167 return; |
| 168 | 168 |
| 169 Send(new AcceleratedVideoEncoderMsg_RequestEncodingParametersChange( | 169 Send(new AcceleratedVideoEncoderMsg_RequestEncodingParametersChange( |
| 170 encoder_route_id_, bitrate, framerate)); | 170 encoder_route_id_, bitrate, framerate)); |
| 171 } | 171 } |
| 172 | 172 |
| 173 void GpuVideoEncodeAcceleratorHost::Destroy() { | 173 void GpuVideoEncodeAcceleratorHost::Destroy() { |
| 174 DCHECK(CalledOnValidThread()); | 174 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 175 if (channel_) | 175 if (channel_) |
| 176 Send(new AcceleratedVideoEncoderMsg_Destroy(encoder_route_id_)); | 176 Send(new AcceleratedVideoEncoderMsg_Destroy(encoder_route_id_)); |
| 177 client_ = nullptr; | 177 client_ = nullptr; |
| 178 delete this; | 178 delete this; |
| 179 } | 179 } |
| 180 | 180 |
| 181 void GpuVideoEncodeAcceleratorHost::OnWillDeleteImpl() { | 181 void GpuVideoEncodeAcceleratorHost::OnWillDeleteImpl() { |
| 182 base::AutoLock lock(impl_lock_); | 182 base::AutoLock lock(impl_lock_); |
| 183 impl_ = nullptr; | 183 impl_ = nullptr; |
| 184 | 184 |
| (...skipping 30 matching lines...) Expand all Loading... |
| 215 VideoFrame::AllocationSize(frame->format(), frame->coded_size()); | 215 VideoFrame::AllocationSize(frame->format(), frame->coded_size()); |
| 216 params.force_keyframe = force_keyframe; | 216 params.force_keyframe = force_keyframe; |
| 217 | 217 |
| 218 Send(new AcceleratedVideoEncoderMsg_Encode(encoder_route_id_, params)); | 218 Send(new AcceleratedVideoEncoderMsg_Encode(encoder_route_id_, params)); |
| 219 } | 219 } |
| 220 | 220 |
| 221 void GpuVideoEncodeAcceleratorHost::PostNotifyError( | 221 void GpuVideoEncodeAcceleratorHost::PostNotifyError( |
| 222 const tracked_objects::Location& location, | 222 const tracked_objects::Location& location, |
| 223 Error error, | 223 Error error, |
| 224 const std::string& message) { | 224 const std::string& message) { |
| 225 DCHECK(CalledOnValidThread()); | 225 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 226 DLOG(ERROR) << "Error from " << location.function_name() << "(" | 226 DLOG(ERROR) << "Error from " << location.function_name() << "(" |
| 227 << location.file_name() << ":" << location.line_number() << ") " | 227 << location.file_name() << ":" << location.line_number() << ") " |
| 228 << message << " (error = " << error << ")"; | 228 << message << " (error = " << error << ")"; |
| 229 // Post the error notification back to this thread, to avoid re-entrancy. | 229 // Post the error notification back to this thread, to avoid re-entrancy. |
| 230 media_task_runner_->PostTask( | 230 media_task_runner_->PostTask( |
| 231 FROM_HERE, base::Bind(&GpuVideoEncodeAcceleratorHost::OnNotifyError, | 231 FROM_HERE, base::Bind(&GpuVideoEncodeAcceleratorHost::OnNotifyError, |
| 232 weak_this_factory_.GetWeakPtr(), error)); | 232 weak_this_factory_.GetWeakPtr(), error)); |
| 233 } | 233 } |
| 234 | 234 |
| 235 void GpuVideoEncodeAcceleratorHost::Send(IPC::Message* message) { | 235 void GpuVideoEncodeAcceleratorHost::Send(IPC::Message* message) { |
| 236 DCHECK(CalledOnValidThread()); | 236 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 237 uint32_t message_type = message->type(); | 237 uint32_t message_type = message->type(); |
| 238 if (!channel_->Send(message)) { | 238 if (!channel_->Send(message)) { |
| 239 PostNotifyError(FROM_HERE, kPlatformFailureError, | 239 PostNotifyError(FROM_HERE, kPlatformFailureError, |
| 240 base::StringPrintf("Send(%d) failed", message_type)); | 240 base::StringPrintf("Send(%d) failed", message_type)); |
| 241 } | 241 } |
| 242 } | 242 } |
| 243 | 243 |
| 244 void GpuVideoEncodeAcceleratorHost::OnRequireBitstreamBuffers( | 244 void GpuVideoEncodeAcceleratorHost::OnRequireBitstreamBuffers( |
| 245 uint32_t input_count, | 245 uint32_t input_count, |
| 246 const gfx::Size& input_coded_size, | 246 const gfx::Size& input_coded_size, |
| 247 uint32_t output_buffer_size) { | 247 uint32_t output_buffer_size) { |
| 248 DCHECK(CalledOnValidThread()); | 248 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 249 DVLOG(2) << __func__ << " input_count=" << input_count | 249 DVLOG(2) << __func__ << " input_count=" << input_count |
| 250 << ", input_coded_size=" << input_coded_size.ToString() | 250 << ", input_coded_size=" << input_coded_size.ToString() |
| 251 << ", output_buffer_size=" << output_buffer_size; | 251 << ", output_buffer_size=" << output_buffer_size; |
| 252 if (client_) { | 252 if (client_) { |
| 253 client_->RequireBitstreamBuffers(input_count, input_coded_size, | 253 client_->RequireBitstreamBuffers(input_count, input_coded_size, |
| 254 output_buffer_size); | 254 output_buffer_size); |
| 255 } | 255 } |
| 256 } | 256 } |
| 257 | 257 |
| 258 void GpuVideoEncodeAcceleratorHost::OnNotifyInputDone(int32_t frame_id) { | 258 void GpuVideoEncodeAcceleratorHost::OnNotifyInputDone(int32_t frame_id) { |
| 259 DCHECK(CalledOnValidThread()); | 259 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 260 DVLOG(3) << __func__ << " frame_id=" << frame_id; | 260 DVLOG(3) << __func__ << " frame_id=" << frame_id; |
| 261 // Fun-fact: std::hash_map is not spec'd to be re-entrant; since freeing a | 261 // Fun-fact: std::hash_map is not spec'd to be re-entrant; since freeing a |
| 262 // frame can trigger a further encode to be kicked off and thus an .insert() | 262 // frame can trigger a further encode to be kicked off and thus an .insert() |
| 263 // back into the map, we separate the frame's dtor running from the .erase() | 263 // back into the map, we separate the frame's dtor running from the .erase() |
| 264 // running by holding on to the frame temporarily. This isn't "just | 264 // running by holding on to the frame temporarily. This isn't "just |
| 265 // theoretical" - Android's std::hash_map crashes if we don't do this. | 265 // theoretical" - Android's std::hash_map crashes if we don't do this. |
| 266 scoped_refptr<VideoFrame> frame = frame_map_[frame_id]; | 266 scoped_refptr<VideoFrame> frame = frame_map_[frame_id]; |
| 267 if (!frame_map_.erase(frame_id)) { | 267 if (!frame_map_.erase(frame_id)) { |
| 268 DLOG(ERROR) << __func__ << " invalid frame_id=" << frame_id; | 268 DLOG(ERROR) << __func__ << " invalid frame_id=" << frame_id; |
| 269 // See OnNotifyError for why this needs to be the last thing in this | 269 // See OnNotifyError for why this needs to be the last thing in this |
| 270 // function. | 270 // function. |
| 271 OnNotifyError(kPlatformFailureError); | 271 OnNotifyError(kPlatformFailureError); |
| 272 return; | 272 return; |
| 273 } | 273 } |
| 274 frame = | 274 frame = |
| 275 nullptr; // Not necessary but nice to be explicit; see fun-fact above. | 275 nullptr; // Not necessary but nice to be explicit; see fun-fact above. |
| 276 } | 276 } |
| 277 | 277 |
| 278 void GpuVideoEncodeAcceleratorHost::OnBitstreamBufferReady( | 278 void GpuVideoEncodeAcceleratorHost::OnBitstreamBufferReady( |
| 279 int32_t bitstream_buffer_id, | 279 int32_t bitstream_buffer_id, |
| 280 uint32_t payload_size, | 280 uint32_t payload_size, |
| 281 bool key_frame, | 281 bool key_frame, |
| 282 base::TimeDelta timestamp) { | 282 base::TimeDelta timestamp) { |
| 283 DCHECK(CalledOnValidThread()); | 283 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 284 DVLOG(3) << __func__ << " bitstream_buffer_id=" << bitstream_buffer_id | 284 DVLOG(3) << __func__ << " bitstream_buffer_id=" << bitstream_buffer_id |
| 285 << ", payload_size=" << payload_size << ", key_frame=" << key_frame; | 285 << ", payload_size=" << payload_size << ", key_frame=" << key_frame; |
| 286 if (client_) | 286 if (client_) |
| 287 client_->BitstreamBufferReady(bitstream_buffer_id, payload_size, key_frame, | 287 client_->BitstreamBufferReady(bitstream_buffer_id, payload_size, key_frame, |
| 288 timestamp); | 288 timestamp); |
| 289 } | 289 } |
| 290 | 290 |
| 291 void GpuVideoEncodeAcceleratorHost::OnNotifyError(Error error) { | 291 void GpuVideoEncodeAcceleratorHost::OnNotifyError(Error error) { |
| 292 DCHECK(CalledOnValidThread()); | 292 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 293 DLOG(ERROR) << __func__ << " error=" << error; | 293 DLOG(ERROR) << __func__ << " error=" << error; |
| 294 if (!client_) | 294 if (!client_) |
| 295 return; | 295 return; |
| 296 weak_this_factory_.InvalidateWeakPtrs(); | 296 weak_this_factory_.InvalidateWeakPtrs(); |
| 297 | 297 |
| 298 // Client::NotifyError() may Destroy() |this|, so calling it needs to be the | 298 // Client::NotifyError() may Destroy() |this|, so calling it needs to be the |
| 299 // last thing done on this stack! | 299 // last thing done on this stack! |
| 300 VideoEncodeAccelerator::Client* client = nullptr; | 300 VideoEncodeAccelerator::Client* client = nullptr; |
| 301 std::swap(client_, client); | 301 std::swap(client_, client); |
| 302 client->NotifyError(error); | 302 client->NotifyError(error); |
| 303 } | 303 } |
| 304 | 304 |
| 305 } // namespace media | 305 } // namespace media |
| OLD | NEW |