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 "wx/ffmpeg_player.h"
33 FFmpegPlayer::FFmpegPlayer (wxWindow* parent)
37 , _video_codec_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")))
48 , _top_crop_in_source (0)
49 , _bottom_crop_in_source (0)
50 , _left_crop_in_source (0)
51 , _right_crop_in_source (0)
56 avcodec_register_all ();
59 _frame = avcodec_alloc_frame ();
61 cerr << "could not allocate frame\n";
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);
73 FFmpegPlayer::~FFmpegPlayer ()
78 sws_freeContext (_scale_context);
81 if (_video_codec_context) {
82 avcodec_close (_video_codec_context);
87 if (_format_context) {
88 avformat_close_input (&_format_context);
93 FFmpegPlayer::timer (wxTimerEvent& ev)
95 if (!can_display ()) {
106 FFmpegPlayer::update_panel ()
113 FFmpegPlayer::decode_frame ()
115 assert (_format_context);
118 int r = av_read_frame (_format_context, &_packet);
123 avcodec_get_frame_defaults (_frame);
124 if (_packet.stream_index == _video_stream) {
126 int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
127 if (r >= 0 && frame_finished) {
129 av_free_packet (&_packet);
134 av_free_packet (&_packet);
139 FFmpegPlayer::convert_frame ()
141 if (!_scale_context || !_rgb[0] || !_frame_valid) {
147 _frame->data, _frame->linesize,
148 0, _video_codec_context->height,
152 uint8_t* in = _rgb[0];
153 uint8_t* out = _rgb[0];
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;
165 FFmpegPlayer::paint_panel (wxPaintEvent& ev)
167 wxPaintDC dc (_panel);
169 wxImage i (cropped_width_in_view(), cropped_height_in_view(), _rgb[0], true);
171 dc.DrawBitmap (b, 0, 0);
175 FFmpegPlayer::slider_moved (wxCommandEvent& ev)
177 if (!can_display ()) {
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);
193 FFmpegPlayer::frames_per_second () const
195 assert (_format_context);
197 AVStream* s = _format_context->streams[_video_stream];
199 if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
200 return av_q2d (s->avg_frame_rate);
203 return av_q2d (s->r_frame_rate);
207 FFmpegPlayer::allocate_buffer_and_scaler ()
209 if (!_format_context || !_panel_width || !_panel_height) {
213 float const panel_ratio = static_cast<float> (_panel_width) / _panel_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;
222 /* panel is more widescreen than the target ratio; clamp height */
223 new_height = _panel_height;
224 new_width = new_height * _ratio;
227 if (new_width == 0 || new_height == 0) {
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());
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;
240 if (_scale_context) {
241 sws_freeContext (_scale_context);
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,
250 if (!_scale_context) {
251 cout << "could not allocate sc\n";
256 FFmpegPlayer::width_source_to_view_scaling () const
258 return static_cast<float> (_full_width) / _video_codec_context->width;
262 FFmpegPlayer::height_source_to_view_scaling () const
264 return static_cast<float> (_full_height) / _video_codec_context->height;
268 FFmpegPlayer::cropped_width_in_view () const
270 return _full_width - ((_left_crop_in_source + _right_crop_in_source) * width_source_to_view_scaling());
274 FFmpegPlayer::cropped_height_in_view () const
276 return _full_height - ((_top_crop_in_source + _bottom_crop_in_source) * height_source_to_view_scaling());
280 FFmpegPlayer::left_crop_in_view () const
282 return _left_crop_in_source * width_source_to_view_scaling();
286 FFmpegPlayer::top_crop_in_view () const
288 return _top_crop_in_source * height_source_to_view_scaling();
292 FFmpegPlayer::panel_sized (wxSizeEvent& ev)
294 _panel_width = ev.GetSize().GetWidth();
295 _panel_height = ev.GetSize().GetHeight();
296 allocate_buffer_and_scaler ();
303 FFmpegPlayer::set_file (string f)
305 if (_video_codec_context) {
306 avcodec_close (_video_codec_context);
309 if (_format_context) {
310 avformat_close_input (&_format_context);
313 if (avformat_open_input (&_format_context, f.c_str(), 0, 0) < 0) {
314 cerr << "avformat_open_input failed.\n";
317 if (avformat_find_stream_info (_format_context, 0) < 0) {
318 cerr << "avformat_find_stream_info failed.\n";
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) {
325 } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
330 if (_video_stream < 0) {
331 cerr << "could not find video stream\n";
334 _video_codec_context = _format_context->streams[_video_stream]->codec;
335 _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
337 if (_video_codec == 0) {
338 cerr << "could not find video decoder\n";
341 if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
342 cerr << "could not open video decoder\n";
345 allocate_buffer_and_scaler ();
351 FFmpegPlayer::set_top_crop (int t)
353 _top_crop_in_source = t;
360 FFmpegPlayer::set_bottom_crop (int b)
362 _bottom_crop_in_source = b;
369 FFmpegPlayer::set_left_crop (int l)
371 _left_crop_in_source = l;
378 FFmpegPlayer::set_right_crop (int r)
380 _right_crop_in_source = r;
387 FFmpegPlayer::set_ratio (float r)
390 allocate_buffer_and_scaler ();
397 FFmpegPlayer::play_clicked (wxCommandEvent &)
403 FFmpegPlayer::check_play_state ()
405 if (_play_button->GetValue()) {
406 _timer.Start (1000 / frames_per_second());
413 FFmpegPlayer::can_display () const
415 return (_format_context && _scale_context);