Basic ffmpeg film viewer.
[dcpomatic.git] / src / wx / ffmpeg_player.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <wx/wx.h>
21 #include <wx/tglbtn.h>
22 #include <iostream>
23 #include <stdint.h>
24 extern "C" {
25 #include <libavcodec/avcodec.h> 
26 #include <libavformat/avformat.h>
27 #include <libswscale/swscale.h>
28 }
29 #include "wx/ffmpeg_player.h"
30
31 using namespace std;
32
33 FFmpegPlayer::FFmpegPlayer (wxWindow* parent)
34         : _format_context (0)
35         , _video_stream (-1)
36         , _frame (0)
37         , _video_codec_context (0)
38         , _video_codec (0)
39         , _scale_context (0)
40         , _frame_valid (false)
41         , _panel (new wxPanel (parent))
42         , _slider (new wxSlider (parent, wxID_ANY, 0, 0, 4096))
43         , _play_button (new wxToggleButton (parent, wxID_ANY, wxT ("Play")))
44         , _panel_width (0)
45         , _panel_height (0)
46         , _full_width (0)
47         , _full_height (0)
48         , _top_crop_in_source (0)
49         , _bottom_crop_in_source (0)
50         , _left_crop_in_source (0)
51         , _right_crop_in_source (0)
52         , _ratio (1.85)
53 {
54         _rgb[0] = 0;
55
56         avcodec_register_all ();
57         av_register_all ();
58         
59         _frame = avcodec_alloc_frame ();
60         if (!_frame) {
61                 cerr << "could not allocate frame\n";
62         }
63         
64         _panel->Bind (wxEVT_PAINT, &FFmpegPlayer::paint_panel, this);
65         _panel->Bind (wxEVT_SIZE, &FFmpegPlayer::panel_sized, this);
66         _slider->Bind (wxEVT_SCROLL_THUMBTRACK, &FFmpegPlayer::slider_moved, this);
67         _slider->Bind (wxEVT_SCROLL_PAGEUP, &FFmpegPlayer::slider_moved, this);
68         _slider->Bind (wxEVT_SCROLL_PAGEDOWN, &FFmpegPlayer::slider_moved, this);
69         _play_button->Bind (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &FFmpegPlayer::play_clicked, this);
70         _timer.Bind (wxEVT_TIMER, &FFmpegPlayer::timer, this);
71 }
72
73 FFmpegPlayer::~FFmpegPlayer ()
74 {
75         delete[] _rgb[0];
76         
77         if (_scale_context) {
78                 sws_freeContext (_scale_context);
79         }
80         
81         if (_video_codec_context) {
82                 avcodec_close (_video_codec_context);
83         }
84         
85         av_free (_frame);
86
87         if (_format_context) {
88                 avformat_close_input (&_format_context);
89         }
90 }
91
92 void
93 FFmpegPlayer::timer (wxTimerEvent& ev)
94 {
95         if (!can_display ()) {
96                 return;
97         }
98         
99         _panel->Refresh ();
100         _panel->Update ();
101         decode_frame ();
102         convert_frame ();
103 }
104
105 void
106 FFmpegPlayer::update_panel ()
107 {
108         _panel->Refresh ();
109         _panel->Update ();
110 }
111
112 void
113 FFmpegPlayer::decode_frame ()
114 {
115         assert (_format_context);
116         
117         while (1) {
118                 int r = av_read_frame (_format_context, &_packet);
119                 if (r < 0) {
120                         return;
121                 }
122                 
123                 avcodec_get_frame_defaults (_frame);
124                 if (_packet.stream_index == _video_stream) {
125                         int frame_finished;
126                         int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
127                         if (r >= 0 && frame_finished) {
128                                 _frame_valid = true;
129                                 av_free_packet (&_packet);
130                                 return;
131                         }
132                 }
133                 
134                 av_free_packet (&_packet);
135         }
136 }
137
138 void
139 FFmpegPlayer::convert_frame ()
140 {
141         if (!_scale_context || !_rgb[0] || !_frame_valid) {
142                 return;
143         }
144         
145         sws_scale (
146                 _scale_context,
147                 _frame->data, _frame->linesize,
148                 0, _video_codec_context->height,
149                 _rgb, _rgb_stride
150                 );
151         
152         uint8_t* in = _rgb[0];
153         uint8_t* out = _rgb[0];
154         
155         in += top_crop_in_view() * _full_width * 3;
156         for (int y = 0; y < cropped_height_in_view(); ++y) {
157                 /* in is the start of the appropriate full-width line */
158                 memmove (out, in + left_crop_in_view() * 3, cropped_width_in_view() * 3);
159                 in += _full_width * 3;
160                 out += cropped_width_in_view() * 3;
161         }
162 }
163
164 void
165 FFmpegPlayer::paint_panel (wxPaintEvent& ev)
166 {
167         wxPaintDC dc (_panel);
168         
169         wxImage i (cropped_width_in_view(), cropped_height_in_view(), _rgb[0], true);
170         wxBitmap b (i);
171         dc.DrawBitmap (b, 0, 0);
172 }
173
174 void
175 FFmpegPlayer::slider_moved (wxCommandEvent& ev)
176 {
177         if (!can_display ()) {
178                 return;
179         }
180         
181         int const video_length_in_frames = (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
182         int const new_frame = video_length_in_frames * _slider->GetValue() / 4096;
183         int64_t const t = static_cast<int64_t>(new_frame) / (av_q2d (_format_context->streams[_video_stream]->time_base) * frames_per_second());
184         av_seek_frame (_format_context, _video_stream, t, 0);
185         avcodec_flush_buffers (_video_codec_context);
186
187         decode_frame ();
188         convert_frame ();
189         update_panel ();
190 }
191
192 float
193 FFmpegPlayer::frames_per_second () const
194 {
195         assert (_format_context);
196         
197         AVStream* s = _format_context->streams[_video_stream];
198         
199         if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
200                 return av_q2d (s->avg_frame_rate);
201         }
202         
203         return av_q2d (s->r_frame_rate);
204 }
205
206 void
207 FFmpegPlayer::allocate_buffer_and_scaler ()
208 {
209         if (!_format_context || !_panel_width || !_panel_height) {
210                 return;
211         }
212
213         float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
214         
215         int new_width;
216         int new_height;
217         if (panel_ratio < _ratio) {
218                 /* panel is less widescreen than the target ratio; clamp width */
219                 new_width = _panel_width;
220                 new_height = new_width / _ratio;
221         } else {
222                 /* panel is more widescreen than the target ratio; clamp height */
223                 new_height = _panel_height;
224                 new_width = new_height * _ratio;
225         }
226
227         if (new_width == 0 || new_height == 0) {
228              return;
229         }
230         
231         _full_height = new_height + ((_top_crop_in_source + _bottom_crop_in_source) * height_source_to_view_scaling());
232         _full_width = new_width + ((_left_crop_in_source + _right_crop_in_source) * width_source_to_view_scaling());
233         
234         delete[] _rgb[0];
235         
236         _rgb[0] = new uint8_t[_full_width * _full_height * 3];
237         memset (_rgb[0], 0, _full_width * _full_height * 3);
238         _rgb_stride[0] = _full_width * 3;
239         
240         if (_scale_context) {
241                 sws_freeContext (_scale_context);
242         }
243         
244         _scale_context = sws_getContext (
245                 _video_codec_context->width, _video_codec_context->height, _video_codec_context->pix_fmt,
246                 _full_width, _full_height, PIX_FMT_RGB24,
247                 SWS_BICUBIC, 0, 0, 0
248                 );
249
250         if (!_scale_context) {
251                 cout << "could not allocate sc\n";
252         }
253 }
254
255 float
256 FFmpegPlayer::width_source_to_view_scaling () const
257 {
258         return static_cast<float> (_full_width) / _video_codec_context->width;
259 }
260
261 float
262 FFmpegPlayer::height_source_to_view_scaling () const
263 {
264         return static_cast<float> (_full_height) / _video_codec_context->height;
265 }
266
267 int
268 FFmpegPlayer::cropped_width_in_view () const
269 {
270         return _full_width - ((_left_crop_in_source + _right_crop_in_source) * width_source_to_view_scaling());
271 }
272
273 int
274 FFmpegPlayer::cropped_height_in_view () const
275 {
276         return _full_height - ((_top_crop_in_source + _bottom_crop_in_source) * height_source_to_view_scaling());
277 }
278
279 int
280 FFmpegPlayer::left_crop_in_view () const
281 {
282         return _left_crop_in_source * width_source_to_view_scaling();
283 }
284
285 int
286 FFmpegPlayer::top_crop_in_view () const
287 {
288         return _top_crop_in_source * height_source_to_view_scaling();
289 }
290
291 void
292 FFmpegPlayer::panel_sized (wxSizeEvent& ev)
293 {
294         _panel_width = ev.GetSize().GetWidth();
295         _panel_height = ev.GetSize().GetHeight();
296         allocate_buffer_and_scaler ();
297
298         convert_frame ();
299         update_panel ();
300 }
301
302 void
303 FFmpegPlayer::set_file (string f)
304 {
305         if (_video_codec_context) {
306                 avcodec_close (_video_codec_context);
307         }
308
309         if (_format_context) {
310                 avformat_close_input (&_format_context);
311         }
312         
313         if (avformat_open_input (&_format_context, f.c_str(), 0, 0) < 0) {
314                 cerr << "avformat_open_input failed.\n";
315         }
316         
317         if (avformat_find_stream_info (_format_context, 0) < 0) {
318                 cerr << "avformat_find_stream_info failed.\n";
319         }
320         
321         for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
322                 AVStream* s = _format_context->streams[i];
323                 if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
324                         _video_stream = i;
325                 } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
326                         /* XXX */
327                 }
328         }
329         
330         if (_video_stream < 0) {
331                 cerr << "could not find video stream\n";
332         }
333         
334         _video_codec_context = _format_context->streams[_video_stream]->codec;
335         _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
336         
337         if (_video_codec == 0) {
338                 cerr << "could not find video decoder\n";
339         }
340         
341         if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
342                 cerr << "could not open video decoder\n";
343         }
344
345         allocate_buffer_and_scaler ();
346         check_play_state ();
347         update_panel ();
348 }
349
350 void
351 FFmpegPlayer::set_top_crop (int t)
352 {
353         _top_crop_in_source = t;
354
355         convert_frame ();
356         update_panel ();
357 }
358
359 void
360 FFmpegPlayer::set_bottom_crop (int b)
361 {
362         _bottom_crop_in_source = b;
363
364         convert_frame ();
365         update_panel ();
366 }
367
368 void
369 FFmpegPlayer::set_left_crop (int l)
370 {
371         _left_crop_in_source = l;
372
373         convert_frame ();
374         update_panel ();
375 }
376
377 void
378 FFmpegPlayer::set_right_crop (int r)
379 {
380         _right_crop_in_source = r;
381
382         convert_frame ();
383         update_panel ();
384 }
385
386 void
387 FFmpegPlayer::set_ratio (float r)
388 {
389         _ratio = r;
390         allocate_buffer_and_scaler ();
391
392         convert_frame ();
393         update_panel ();
394 }
395
396 void
397 FFmpegPlayer::play_clicked (wxCommandEvent &)
398 {
399         check_play_state ();
400 }
401
402 void
403 FFmpegPlayer::check_play_state ()
404 {
405         if (_play_button->GetValue()) {
406                 _timer.Start (1000 / frames_per_second());
407         } else {
408                 _timer.Stop ();
409         }
410 }
411
412 bool
413 FFmpegPlayer::can_display () const
414 {
415         return (_format_context && _scale_context);
416 }
417