2 Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
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.
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.
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.
21 #include <wx/tglbtn.h>
25 #include <libavcodec/avcodec.h>
26 #include <libavformat/avformat.h>
27 #include <libswscale/swscale.h>
29 #include "lib/exceptions.h"
30 #include "wx/ffmpeg_player.h"
34 FFmpegPlayer::FFmpegPlayer (wxWindow* parent)
38 , _video_codec_context (0)
41 , _frame_valid (false)
42 , _last_frame_in_seconds (0)
43 , _panel (new wxPanel (parent))
44 , _slider (new wxSlider (parent, wxID_ANY, 0, 0, 4096))
45 , _play_button (new wxToggleButton (parent, wxID_ANY, wxT ("Play")))
50 , _top_crop_in_source (0)
51 , _bottom_crop_in_source (0)
52 , _left_crop_in_source (0)
53 , _right_crop_in_source (0)
58 avcodec_register_all ();
61 _frame = avcodec_alloc_frame ();
63 throw DecodeError ("could not allocate frame");
66 _panel->Bind (wxEVT_PAINT, &FFmpegPlayer::paint_panel, this);
67 _panel->Bind (wxEVT_SIZE, &FFmpegPlayer::panel_sized, this);
68 _slider->Bind (wxEVT_SCROLL_THUMBTRACK, &FFmpegPlayer::slider_moved, this);
69 _slider->Bind (wxEVT_SCROLL_PAGEUP, &FFmpegPlayer::slider_moved, this);
70 _slider->Bind (wxEVT_SCROLL_PAGEDOWN, &FFmpegPlayer::slider_moved, this);
71 _play_button->Bind (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &FFmpegPlayer::play_clicked, this);
72 _timer.Bind (wxEVT_TIMER, &FFmpegPlayer::timer, this);
75 FFmpegPlayer::~FFmpegPlayer ()
80 sws_freeContext (_scale_context);
83 if (_video_codec_context) {
84 avcodec_close (_video_codec_context);
89 if (_format_context) {
90 avformat_close_input (&_format_context);
95 FFmpegPlayer::timer (wxTimerEvent& ev)
97 if (!can_display ()) {
106 double const video_length_in_seconds = static_cast<double>(_format_context->duration) / AV_TIME_BASE;
107 if (_last_frame_in_seconds) {
108 int const new_slider_position = 4096 * _last_frame_in_seconds / video_length_in_seconds;
109 if (new_slider_position != _slider->GetValue()) {
110 _slider->SetValue (new_slider_position);
116 FFmpegPlayer::update_panel ()
123 FFmpegPlayer::decode_frame ()
125 assert (_format_context);
128 int r = av_read_frame (_format_context, &_packet);
133 avcodec_get_frame_defaults (_frame);
134 if (_packet.stream_index == _video_stream) {
136 int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
137 if (r >= 0 && frame_finished) {
139 _last_frame_in_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
140 * av_frame_get_best_effort_timestamp(_frame);
141 av_free_packet (&_packet);
146 av_free_packet (&_packet);
151 FFmpegPlayer::convert_frame ()
153 if (!_scale_context || !_rgb[0] || !_frame_valid) {
159 _frame->data, _frame->linesize,
160 0, _video_codec_context->height,
164 uint8_t* in = _rgb[0];
165 uint8_t* out = _rgb[0];
167 in += top_crop_in_view() * _full_width * 3;
168 for (int y = 0; y < cropped_height_in_view(); ++y) {
169 /* in is the start of the appropriate full-width line */
170 memmove (out, in + left_crop_in_view() * 3, cropped_width_in_view() * 3);
171 in += _full_width * 3;
172 out += cropped_width_in_view() * 3;
177 FFmpegPlayer::paint_panel (wxPaintEvent& ev)
179 wxPaintDC dc (_panel);
181 wxImage i (cropped_width_in_view(), cropped_height_in_view(), _rgb[0], true);
183 dc.DrawBitmap (b, 0, 0);
187 FFmpegPlayer::slider_moved (wxCommandEvent& ev)
189 if (!can_display ()) {
193 double const video_length_in_seconds = static_cast<double>(_format_context->duration) / AV_TIME_BASE;
194 double const new_position_in_seconds = video_length_in_seconds * _slider->GetValue() / 4096;
195 int64_t const t = static_cast<int64_t>(new_position_in_seconds) / av_q2d (_format_context->streams[_video_stream]->time_base);
196 av_seek_frame (_format_context, _video_stream, t, 0);
197 avcodec_flush_buffers (_video_codec_context);
205 FFmpegPlayer::frames_per_second () const
207 assert (_format_context);
209 AVStream* s = _format_context->streams[_video_stream];
211 if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
212 return av_q2d (s->avg_frame_rate);
215 return av_q2d (s->r_frame_rate);
219 FFmpegPlayer::allocate_buffer_and_scaler ()
221 if (!_format_context || !_panel_width || !_panel_height) {
225 float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
229 if (panel_ratio < _ratio) {
230 /* panel is less widescreen than the target ratio; clamp width */
231 new_width = _panel_width;
232 new_height = new_width / _ratio;
234 /* panel is more widescreen than the target ratio; clamp height */
235 new_height = _panel_height;
236 new_width = new_height * _ratio;
239 if (new_width == 0 || new_height == 0) {
243 _full_height = new_height + ((_top_crop_in_source + _bottom_crop_in_source) * height_source_to_view_scaling());
244 _full_width = new_width + ((_left_crop_in_source + _right_crop_in_source) * width_source_to_view_scaling());
248 _rgb[0] = new uint8_t[_full_width * _full_height * 3];
249 memset (_rgb[0], 0, _full_width * _full_height * 3);
250 _rgb_stride[0] = _full_width * 3;
252 if (_scale_context) {
253 sws_freeContext (_scale_context);
256 _scale_context = sws_getContext (
257 _video_codec_context->width, _video_codec_context->height, _video_codec_context->pix_fmt,
258 _full_width, _full_height, PIX_FMT_RGB24,
262 if (!_scale_context) {
263 throw DecodeError ("could not create scaler");
268 FFmpegPlayer::width_source_to_view_scaling () const
270 return static_cast<float> (_full_width) / _video_codec_context->width;
274 FFmpegPlayer::height_source_to_view_scaling () const
276 return static_cast<float> (_full_height) / _video_codec_context->height;
280 FFmpegPlayer::cropped_width_in_view () const
282 return _full_width - ((_left_crop_in_source + _right_crop_in_source) * width_source_to_view_scaling());
286 FFmpegPlayer::cropped_height_in_view () const
288 return _full_height - ((_top_crop_in_source + _bottom_crop_in_source) * height_source_to_view_scaling());
292 FFmpegPlayer::left_crop_in_view () const
294 return _left_crop_in_source * width_source_to_view_scaling();
298 FFmpegPlayer::top_crop_in_view () const
300 return _top_crop_in_source * height_source_to_view_scaling();
304 FFmpegPlayer::panel_sized (wxSizeEvent& ev)
306 _panel_width = ev.GetSize().GetWidth();
307 _panel_height = ev.GetSize().GetHeight();
308 allocate_buffer_and_scaler ();
315 FFmpegPlayer::set_file (string f)
317 if (_video_codec_context) {
318 avcodec_close (_video_codec_context);
321 if (_format_context) {
322 avformat_close_input (&_format_context);
325 if (avformat_open_input (&_format_context, f.c_str(), 0, 0) < 0) {
326 throw OpenFileError (f);
329 if (avformat_find_stream_info (_format_context, 0) < 0) {
330 throw DecodeError ("could not find stream information");
333 for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
334 AVStream* s = _format_context->streams[i];
335 if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
337 } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
342 if (_video_stream < 0) {
343 throw DecodeError ("could not find video stream");
346 _video_codec_context = _format_context->streams[_video_stream]->codec;
347 _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
349 if (_video_codec == 0) {
350 throw DecodeError ("could not find video stream");
353 if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
354 throw DecodeError ("could not open video decoder");
357 allocate_buffer_and_scaler ();
363 FFmpegPlayer::set_top_crop (int t)
365 _top_crop_in_source = t;
367 allocate_buffer_and_scaler ();
373 FFmpegPlayer::set_bottom_crop (int b)
375 _bottom_crop_in_source = b;
377 allocate_buffer_and_scaler ();
383 FFmpegPlayer::set_left_crop (int l)
385 _left_crop_in_source = l;
387 allocate_buffer_and_scaler ();
393 FFmpegPlayer::set_right_crop (int r)
395 _right_crop_in_source = r;
397 allocate_buffer_and_scaler ();
403 FFmpegPlayer::set_ratio (float r)
407 allocate_buffer_and_scaler ();
413 FFmpegPlayer::play_clicked (wxCommandEvent &)
419 FFmpegPlayer::check_play_state ()
421 if (_play_button->GetValue()) {
422 _timer.Start (1000 / frames_per_second());
429 FFmpegPlayer::can_display () const
431 return (_format_context && _scale_context);