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_base.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, scoped_ptr<uint8[]> init_data, 99 int init_data_size) { 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::VideoRendererBase( 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 true)); 141 collection->SetAudioRenderer(audio_renderer.Pass()); 142 143 base::WaitableEvent event(true, false); 144 media::PipelineStatus status; 145 146 pipeline->Start( 147 collection.Pass(), base::Closure(), media::PipelineStatusCB(), 148 base::Bind(&SaveStatusAndSignal, &event, &status), 149 base::Bind(&OnBufferingState), base::Closure()); 150 151 // Wait until the pipeline is fully initialized. 152 event.Wait(); 153 CHECK_EQ(status, media::PIPELINE_OK) << "Pipeline initialization failed"; 154 155 // And start the playback. 156 pipeline->SetPlaybackRate(1.0f); 157 } 158 159 void TerminateHandler(int signal) { 160 g_running = false; 161 } 162 163 void PeriodicalUpdate( 164 media::Pipeline* pipeline, 165 base::MessageLoop* message_loop, 166 bool audio_only) { 167 if (!g_running) { 168 // interrupt signal was received during last time period. 169 // Quit message_loop only when pipeline is fully stopped. 170 pipeline->Stop(base::MessageLoop::QuitClosure()); 171 return; 172 } 173 174 // Consume all the X events 175 while (XPending(g_display)) { 176 XEvent e; 177 XNextEvent(g_display, &e); 178 switch (e.type) { 179 case ButtonPress: 180 { 181 Window window; 182 int x, y; 183 unsigned int width, height, border_width, depth; 184 XGetGeometry(g_display, 185 g_window, 186 &window, 187 &x, 188 &y, 189 &width, 190 &height, 191 &border_width, 192 &depth); 193 base::TimeDelta time = pipeline->GetMediaDuration(); 194 pipeline->Seek(time*e.xbutton.x/width, media::PipelineStatusCB()); 195 } 196 break; 197 case KeyPress: 198 { 199 KeySym key = XkbKeycodeToKeysym(g_display, e.xkey.keycode, 0, 0); 200 if (key == XK_Escape) { 201 g_running = false; 202 // Quit message_loop only when pipeline is fully stopped. 203 pipeline->Stop(base::MessageLoop::QuitClosure()); 204 return; 205 } else if (key == XK_space) { 206 if (pipeline->GetPlaybackRate() < 0.01f) // paused 207 pipeline->SetPlaybackRate(1.0f); 208 else 209 pipeline->SetPlaybackRate(0.0f); 210 } 211 } 212 break; 213 default: 214 break; 215 } 216 } 217 218 message_loop->PostDelayedTask( 219 FROM_HERE, 220 base::Bind(&PeriodicalUpdate, 221 base::Unretained(pipeline), 222 message_loop, 223 audio_only), 224 base::TimeDelta::FromMilliseconds(10)); 225 } 226 227 int main(int argc, char** argv) { 228 base::AtExitManager at_exit; 229 media::InitializeMediaLibraryForTesting(); 230 231 CommandLine::Init(argc, argv); 232 CommandLine* command_line = CommandLine::ForCurrentProcess(); 233 std::string filename = command_line->GetSwitchValueASCII("file"); 234 235 if (filename.empty()) { 236 std::cout << "Usage: " << argv[0] << " --file=FILE" << std::endl 237 << std::endl 238 << "Optional arguments:" << std::endl 239 << " [--audio]" 240 << " [--alsa-device=DEVICE]" 241 << " [--use-gl]" 242 << " [--streaming]" << std::endl 243 << " Press [ESC] to stop" << std::endl 244 << " Press [SPACE] to toggle pause/play" << std::endl 245 << " Press mouse left button to seek" << std::endl; 246 return 1; 247 } 248 249 scoped_ptr<media::AudioManager> audio_manager(media::AudioManager::Create()); 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