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 3 // found in the LICENSE file. 4 5 #include <signal.h> 6 7 #include <iostream> // NOLINT 8 9 #include "base/at_exit.h" 10 #include "base/bind.h" 11 #include "base/command_line.h" 12 #include "base/files/file_path.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/memory/scoped_vector.h" 15 #include "base/threading/platform_thread.h" 16 #include "base/threading/thread.h" 17 #include "media/audio/audio_manager.h" 18 #include "media/audio/null_audio_sink.h" 19 #include "media/base/decryptor.h" 20 #include "media/base/filter_collection.h" 21 #include "media/base/media.h" 22 #include "media/base/media_log.h" 23 #include "media/base/media_switches.h" 24 #include "media/base/pipeline.h" 25 #include "media/base/video_frame.h" 26 #include "media/filters/audio_renderer_impl.h" 27 #include "media/filters/ffmpeg_audio_decoder.h" 28 #include "media/filters/ffmpeg_demuxer.h" 29 #include "media/filters/ffmpeg_video_decoder.h" 30 #include "media/filters/file_data_source.h" 31 #include "media/filters/video_renderer_impl.h" 32 #include "media/tools/player_x11/data_source_logger.h" 33 34 // Include X11 headers here because X11/Xlib.h #define's Status 35 // which causes compiler errors with Status enum declarations 36 // in media::DemuxerStream & media::AudioDecoder. 37 #include <X11/XKBlib.h> 38 #include <X11/Xlib.h> 39 #include "media/tools/player_x11/gl_video_renderer.h" 40 #include "media/tools/player_x11/x11_video_renderer.h" 41 42 static Display* g_display = NULL; 43 static Window g_window = 0; 44 static bool g_running = false; 45 46 media::AudioManager* g_audio_manager = NULL; 47 48 scoped_ptr<media::DataSource> CreateDataSource(const std::string& file_path) { 49 media::FileDataSource* file_data_source = new media::FileDataSource(); 50 CHECK(file_data_source->Initialize(base::FilePath(file_path))); 51 52 scoped_ptr<media::DataSource> data_source(file_data_source); 53 return data_source.Pass(); 54 } 55 56 // Initialize X11. Returns true if successful. This method creates the X11 57 // window. Further initialization is done in X11VideoRenderer. 58 bool InitX11() { 59 g_display = XOpenDisplay(NULL); 60 if (!g_display) { 61 std::cout << "Error - cannot open display" << std::endl; 62 return false; 63 } 64 65 // Get properties of the screen. 66 int screen = DefaultScreen(g_display); 67 int root_window = RootWindow(g_display, screen); 68 69 // Creates the window. 70 g_window = XCreateSimpleWindow(g_display, root_window, 1, 1, 100, 50, 0, 71 BlackPixel(g_display, screen), 72 BlackPixel(g_display, screen)); 73 XStoreName(g_display, g_window, "X11 Media Player"); 74 75 XSelectInput(g_display, g_window, 76 ExposureMask | ButtonPressMask | KeyPressMask); 77 XMapWindow(g_display, g_window); 78 return true; 79 } 80 81 void SetOpaque(bool /*opaque*/) { 82 } 83 84 typedef base::Callback<void(media::VideoFrame*)> PaintCB; 85 void Paint(base::MessageLoop* message_loop, const PaintCB& paint_cb, 86 const scoped_refptr<media::VideoFrame>& video_frame) { 87 if (message_loop != base::MessageLoop::current()) { 88 message_loop->PostTask(FROM_HERE, base::Bind( 89 &Paint, message_loop, paint_cb, video_frame)); 90 return; 91 } 92 93 paint_cb.Run(video_frame.get()); 94 } 95 96 static void OnBufferingState(media::Pipeline::BufferingState buffering_state) {} 97 98 static void NeedKey(const std::string& type, 99 const std::vector<uint8>& init_data) { 100 std::cout << "File is encrypted." << std::endl; 101 } 102 103 static void SaveStatusAndSignal(base::WaitableEvent* event, 104 media::PipelineStatus* status_out, 105 media::PipelineStatus status) { 106 *status_out = status; 107 event->Signal(); 108 } 109 110 // TODO(vrk): Re-enabled audio. (crbug.com/112159) 111 void InitPipeline(media::Pipeline* pipeline, 112 const scoped_refptr<base::MessageLoopProxy>& message_loop, 113 media::Demuxer* demuxer, 114 const PaintCB& paint_cb, 115 bool /* enable_audio */, 116 base::MessageLoop* paint_message_loop) { 117 // Create our filter factories. 118 scoped_ptr<media::FilterCollection> collection( 119 new media::FilterCollection()); 120 collection->SetDemuxer(demuxer); 121 122 ScopedVector<media::VideoDecoder> video_decoders; 123 video_decoders.push_back(new media::FFmpegVideoDecoder(message_loop)); 124 scoped_ptr<media::VideoRenderer> video_renderer(new media::VideoRendererImpl( 125 message_loop, 126 video_decoders.Pass(), 127 media::SetDecryptorReadyCB(), 128 base::Bind(&Paint, paint_message_loop, paint_cb), 129 base::Bind(&SetOpaque), 130 true)); 131 collection->SetVideoRenderer(video_renderer.Pass()); 132 133 ScopedVector<media::AudioDecoder> audio_decoders; 134 audio_decoders.push_back(new media::FFmpegAudioDecoder(message_loop)); 135 scoped_ptr<media::AudioRenderer> audio_renderer(new media::AudioRendererImpl( 136 message_loop, 137 new media::NullAudioSink(message_loop), 138 audio_decoders.Pass(), 139 media::SetDecryptorReadyCB())); 140 collection->SetAudioRenderer(audio_renderer.Pass()); 141 142 base::WaitableEvent event(true, false); 143 media::PipelineStatus status; 144 145 pipeline->Start( 146 collection.Pass(), base::Closure(), media::PipelineStatusCB(), 147 base::Bind(&SaveStatusAndSignal, &event, &status), 148 base::Bind(&OnBufferingState), base::Closure()); 149 150 // Wait until the pipeline is fully initialized. 151 event.Wait(); 152 CHECK_EQ(status, media::PIPELINE_OK) << "Pipeline initialization failed"; 153 154 // And start the playback. 155 pipeline->SetPlaybackRate(1.0f); 156 } 157 158 void TerminateHandler(int signal) { 159 g_running = false; 160 } 161 162 void PeriodicalUpdate( 163 media::Pipeline* pipeline, 164 base::MessageLoop* message_loop, 165 bool audio_only) { 166 if (!g_running) { 167 // interrupt signal was received during last time period. 168 // Quit message_loop only when pipeline is fully stopped. 169 pipeline->Stop(base::MessageLoop::QuitClosure()); 170 return; 171 } 172 173 // Consume all the X events 174 while (XPending(g_display)) { 175 XEvent e; 176 XNextEvent(g_display, &e); 177 switch (e.type) { 178 case ButtonPress: 179 { 180 Window window; 181 int x, y; 182 unsigned int width, height, border_width, depth; 183 XGetGeometry(g_display, 184 g_window, 185 &window, 186 &x, 187 &y, 188 &width, 189 &height, 190 &border_width, 191 &depth); 192 base::TimeDelta time = pipeline->GetMediaDuration(); 193 pipeline->Seek(time*e.xbutton.x/width, media::PipelineStatusCB()); 194 } 195 break; 196 case KeyPress: 197 { 198 KeySym key = XkbKeycodeToKeysym(g_display, e.xkey.keycode, 0, 0); 199 if (key == XK_Escape) { 200 g_running = false; 201 // Quit message_loop only when pipeline is fully stopped. 202 pipeline->Stop(base::MessageLoop::QuitClosure()); 203 return; 204 } else if (key == XK_space) { 205 if (pipeline->GetPlaybackRate() < 0.01f) // paused 206 pipeline->SetPlaybackRate(1.0f); 207 else 208 pipeline->SetPlaybackRate(0.0f); 209 } 210 } 211 break; 212 default: 213 break; 214 } 215 } 216 217 message_loop->PostDelayedTask( 218 FROM_HERE, 219 base::Bind(&PeriodicalUpdate, 220 base::Unretained(pipeline), 221 message_loop, 222 audio_only), 223 base::TimeDelta::FromMilliseconds(10)); 224 } 225 226 int main(int argc, char** argv) { 227 base::AtExitManager at_exit; 228 media::InitializeMediaLibraryForTesting(); 229 230 CommandLine::Init(argc, argv); 231 CommandLine* command_line = CommandLine::ForCurrentProcess(); 232 std::string filename = command_line->GetSwitchValueASCII("file"); 233 234 if (filename.empty()) { 235 std::cout << "Usage: " << argv[0] << " --file=FILE" << std::endl 236 << std::endl 237 << "Optional arguments:" << std::endl 238 << " [--audio]" 239 << " [--alsa-device=DEVICE]" 240 << " [--use-gl]" 241 << " [--streaming]" << std::endl 242 << " Press [ESC] to stop" << std::endl 243 << " Press [SPACE] to toggle pause/play" << std::endl 244 << " Press mouse left button to seek" << std::endl; 245 return 1; 246 } 247 248 scoped_ptr<media::AudioManager> audio_manager( 249 media::AudioManager::CreateForTesting()); 250 g_audio_manager = audio_manager.get(); 251 252 logging::LoggingSettings settings; 253 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; 254 logging::InitLogging(settings); 255 256 // Install the signal handler. 257 signal(SIGTERM, &TerminateHandler); 258 signal(SIGINT, &TerminateHandler); 259 260 // Initialize X11. 261 if (!InitX11()) 262 return 1; 263 264 // Initialize the pipeline thread and the pipeline. 265 base::MessageLoop message_loop; 266 base::Thread media_thread("MediaThread"); 267 media_thread.Start(); 268 269 PaintCB paint_cb; 270 if (command_line->HasSwitch("use-gl")) { 271 paint_cb = base::Bind( 272 &GlVideoRenderer::Paint, new GlVideoRenderer(g_display, g_window)); 273 } else { 274 paint_cb = base::Bind( 275 &X11VideoRenderer::Paint, new X11VideoRenderer(g_display, g_window)); 276 } 277 278 scoped_ptr<media::DataSource> data_source(new DataSourceLogger( 279 CreateDataSource(filename), command_line->HasSwitch("streaming"))); 280 scoped_ptr<media::Demuxer> demuxer(new media::FFmpegDemuxer( 281 media_thread.message_loop_proxy(), data_source.get(), 282 base::Bind(&NeedKey), new media::MediaLog())); 283 284 media::Pipeline pipeline(media_thread.message_loop_proxy(), 285 new media::MediaLog()); 286 InitPipeline(&pipeline, media_thread.message_loop_proxy(), demuxer.get(), 287 paint_cb, command_line->HasSwitch("audio"), &message_loop); 288 289 // Main loop of the application. 290 g_running = true; 291 292 message_loop.PostTask(FROM_HERE, base::Bind( 293 &PeriodicalUpdate, base::Unretained(&pipeline), &message_loop, 294 !pipeline.HasVideo())); 295 message_loop.Run(); 296 297 // Cleanup tasks. 298 media_thread.Stop(); 299 300 // Release callback which releases video renderer. Do this before cleaning up 301 // X below since the video renderer has some X cleanup duties as well. 302 paint_cb.Reset(); 303 304 XDestroyWindow(g_display, g_window); 305 XCloseDisplay(g_display); 306 g_audio_manager = NULL; 307 308 return 0; 309 } 310