/* * Copyright © 2018 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Benjamin Otte */ #include "config.h" /* * Sadly, we need this to build on Visual Studio against glib-2.74.x or earlier, * otherwise the build will fail when building the g_io_module_*() bits with error C2375 * (redefinition; different linkage). This must be before including the Gio headers. */ #if defined (_MSC_VER) && defined (MODULES_OVERRIDE_GLIB_EXTERN) # define _GLIB_EXTERN __declspec(dllexport) extern #endif #include "gtkffmediafileprivate.h" #include #include "gdk/gdkmemorytextureprivate.h" #include #include #include #include #include #include #include typedef struct _GtkVideoFrameFFMpeg GtkVideoFrameFFMpeg; struct _GtkVideoFrameFFMpeg { GdkTexture *texture; gint64 timestamp; }; typedef struct _GtkFStream GtkFfStream; struct _GtkFStream { AVCodecContext *codec_ctx; AVStream *stream; int stream_id; int type; }; struct _GtkFfMediaFile { GtkMediaFile parent_instance; GFile *file; GInputStream *input_stream; AVFormatContext *device_ctx; /* used for avdevice audio playback */ AVFormatContext *format_ctx; GtkFfStream *input_audio_stream; GtkFfStream *input_video_stream; GtkFfStream *output_audio_stream; gint64 audio_samples_count; // Resampling struct SwrContext *swr_ctx; AVFrame* audio_frame; // Rescaling struct SwsContext *sws_ctx; enum AVPixelFormat sws_pix_fmt; GdkMemoryFormat memory_format; GtkVideoFrameFFMpeg current_frame; GtkVideoFrameFFMpeg next_frame; gint64 start_time; /* monotonic time when we displayed the last frame */ guint next_frame_cb; /* Source ID of next frame callback */ }; struct _GtkFfMediaFileClass { GtkMediaFileClass parent_class; }; static void gtk_video_frame_ffmpeg_init (GtkVideoFrameFFMpeg *frame, GdkTexture *texture, gint64 timestamp) { frame->texture = texture; frame->timestamp = timestamp; } static void gtk_video_frame_ffmpeg_clear (GtkVideoFrameFFMpeg *frame) { g_clear_object (&frame->texture); frame->timestamp = 0; } static gboolean gtk_video_frame_ffmpeg_is_empty (GtkVideoFrameFFMpeg *frame) { return frame->texture == NULL; } static void gtk_video_frame_ffmpeg_move (GtkVideoFrameFFMpeg *dest, GtkVideoFrameFFMpeg *src) { *dest = *src; src->texture = NULL; src->timestamp = 0; } static void gtk_ff_media_file_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (paintable); if (!gtk_video_frame_ffmpeg_is_empty (&self->current_frame)) { gdk_paintable_snapshot (GDK_PAINTABLE (self->current_frame.texture), snapshot, width, height); } } static GdkPaintable * gtk_ff_media_file_paintable_get_current_image (GdkPaintable *paintable) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (paintable); if (gtk_video_frame_ffmpeg_is_empty (&self->current_frame)) { if (self->input_video_stream->codec_ctx) return gdk_paintable_new_empty (self->input_video_stream->codec_ctx->width, self->input_video_stream->codec_ctx->height); else return gdk_paintable_new_empty (0, 0); } return GDK_PAINTABLE (g_object_ref (self->current_frame.texture)); } static int gtk_ff_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (paintable); if (self->input_video_stream->codec_ctx) return self->input_video_stream->codec_ctx->width; return 0; } static int gtk_ff_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (paintable); if (self->input_video_stream->codec_ctx) return self->input_video_stream->codec_ctx->height; return 0; } static double gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (paintable); if (self->input_video_stream->codec_ctx) return (double) self->input_video_stream->codec_ctx->width / self->input_video_stream->codec_ctx->height; return 0.0; }; static void gtk_ff_media_file_paintable_init (GdkPaintableInterface *iface) { iface->snapshot = gtk_ff_media_file_paintable_snapshot; iface->get_current_image = gtk_ff_media_file_paintable_get_current_image; iface->get_intrinsic_width = gtk_ff_media_file_paintable_get_intrinsic_width; iface->get_intrinsic_height = gtk_ff_media_file_paintable_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio; } G_DEFINE_TYPE_EXTENDED (GtkFfMediaFile, gtk_ff_media_file, GTK_TYPE_MEDIA_FILE, 0, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, gtk_ff_media_file_paintable_init)) G_MODULE_EXPORT void g_io_module_load (GIOModule *module) { g_type_module_use (G_TYPE_MODULE (module)); #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT (58, 9, 100) av_register_all (); #endif g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME, GTK_TYPE_FF_MEDIA_FILE, "ffmpeg", 0); } G_MODULE_EXPORT G_GNUC_NORETURN void g_io_module_unload (GIOModule *module) { g_assert_not_reached (); } G_MODULE_EXPORT char ** g_io_module_query (void) { char *eps[] = { (char *) GTK_MEDIA_FILE_EXTENSION_POINT_NAME, NULL }; return g_strdupv (eps); } static void gtk_ff_stream_close (GtkFfStream *stream) { stream->stream_id = -1; g_clear_pointer (&stream->codec_ctx, avcodec_close); g_free (stream); } static void gtk_ff_media_file_set_ffmpeg_error (GtkFfMediaFile *self, int av_errnum) { char s[AV_ERROR_MAX_STRING_SIZE]; if (gtk_media_stream_get_error (GTK_MEDIA_STREAM (self))) return; if (av_strerror (av_errnum, s, sizeof (s) != 0)) g_snprintf (s, sizeof (s), _("Unspecified error decoding media")); gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_FAILED, "%s", s); } static GtkFfStream * gtk_ff_media_file_find_input_stream (GtkFfMediaFile *self, int type) { GtkFfStream *ff_stream; const AVCodec *codec; AVCodecContext *codec_ctx; AVStream *stream; int stream_id; int errnum; stream_id = av_find_best_stream (self->format_ctx, type, -1, -1, NULL, 0); if (stream_id < 0) { return NULL; } stream = self->format_ctx->streams[stream_id]; codec = avcodec_find_decoder (stream->codecpar->codec_id); if (codec == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot find decoder: %s"), avcodec_get_name (stream->codecpar->codec_id)); return NULL; } codec_ctx = avcodec_alloc_context3 (codec); if (codec_ctx == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Failed to allocate a codec context")); return NULL; } errnum = avcodec_parameters_to_context (codec_ctx, stream->codecpar); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); avcodec_close (codec_ctx); return NULL; } errnum = avcodec_open2 (codec_ctx, codec, &stream->metadata); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); avcodec_close (codec_ctx); return NULL; } ff_stream = g_new (GtkFfStream, 1); ff_stream->codec_ctx = codec_ctx; ff_stream->stream = stream; ff_stream->stream_id = stream_id; ff_stream->type = type; return ff_stream; } static GtkFfStream * gtk_ff_media_file_add_output_stream (GtkFfMediaFile *self, AVFormatContext *fmt_ctx, enum AVCodecID codec_id) { GtkFfStream *ff_media_stream; const AVCodec *codec; AVCodecContext *codec_ctx; AVStream *stream; int stream_id; int errnum; // find the encoder codec = avcodec_find_encoder (codec_id); if (codec == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot find encoder: %s"), avcodec_get_name (codec_id)); return NULL; } stream = avformat_new_stream (fmt_ctx, NULL); if (stream == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot add new stream")); return NULL; } stream_id = fmt_ctx->nb_streams - 1; codec_ctx = avcodec_alloc_context3 (codec); if (codec_ctx == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Failed to allocate a codec context")); return NULL; } // set the encoder options codec_ctx->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_S16; codec_ctx->sample_rate = codec->supported_samplerates ? codec->supported_samplerates[0] : 48000; codec_ctx->channel_layout = codec->channel_layouts ? codec->channel_layouts[0] : AV_CH_LAYOUT_STEREO; codec_ctx->channels = av_get_channel_layout_nb_channels (codec_ctx->channel_layout); stream->time_base = (AVRational){ 1, codec_ctx->sample_rate }; // open the codec errnum = avcodec_open2 (codec_ctx, codec, NULL); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); avcodec_close (codec_ctx); return NULL; } errnum = avcodec_parameters_from_context (stream->codecpar, codec_ctx); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); avcodec_close (codec_ctx); return NULL; } ff_media_stream = g_new (GtkFfStream, 1); ff_media_stream->codec_ctx = codec_ctx; ff_media_stream->stream = stream; ff_media_stream->stream_id = stream_id; ff_media_stream->type = AVMEDIA_TYPE_AUDIO; return ff_media_stream; } static gboolean gtk_ff_media_file_seek_stream (GtkFfMediaFile *self, GtkFfStream *stream, int64_t timestamp) { int errnum; if (!stream) return TRUE; errnum = av_seek_frame (self->format_ctx, stream->stream_id, av_rescale_q (timestamp, (AVRational){ 1, G_USEC_PER_SEC }, stream->stream->time_base), AVSEEK_FLAG_BACKWARD); if (errnum < 0) { gtk_media_stream_seek_failed (GTK_MEDIA_STREAM (self)); return FALSE; } return TRUE; } static AVFrame * gtk_ff_media_file_alloc_audio_frame (enum AVSampleFormat sample_fmt, uint64_t channel_layout, int sample_rate, int nb_samples) { AVFrame *frame = av_frame_alloc (); int ret; if (!frame) { return NULL; } frame->format = sample_fmt; frame->channel_layout = channel_layout; frame->sample_rate = sample_rate; frame->nb_samples = nb_samples; if (nb_samples) { ret = av_frame_get_buffer (frame, 0); if (ret < 0) { return NULL; } } return frame; } static void gtk_ff_media_file_write_audio_frame (GtkFfMediaFile *self, AVFrame* frame) { AVFormatContext *device_ctx; AVCodecContext *codec_ctx; AVStream *stream; AVFrame *resampled_frame; int errnum; int dst_nb_samples; device_ctx = self->device_ctx; codec_ctx = self->output_audio_stream->codec_ctx; stream = self->output_audio_stream->stream; if (frame) { dst_nb_samples = av_rescale_rnd (swr_get_delay (self->swr_ctx, codec_ctx->sample_rate) + frame->nb_samples, codec_ctx->sample_rate, codec_ctx->sample_rate, AV_ROUND_UP); resampled_frame = gtk_ff_media_file_alloc_audio_frame (codec_ctx->sample_fmt, codec_ctx->channel_layout, codec_ctx->sample_rate, dst_nb_samples); if (resampled_frame == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Failed to allocate an audio frame")); return; } errnum = swr_convert (self->swr_ctx, resampled_frame->data, dst_nb_samples, (const uint8_t **) frame->data, frame->nb_samples); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return; } frame = resampled_frame; frame->pts = av_rescale_q (self->audio_samples_count, (AVRational){ 1, codec_ctx->sample_rate }, codec_ctx->time_base); errnum = av_write_uncoded_frame (device_ctx, stream->index, frame); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return; } self->audio_samples_count += frame->nb_samples; } } static int gtk_ff_media_file_read_packet_cb (void *data, uint8_t *buf, int buf_size) { GtkFfMediaFile *self = data; GError *error = NULL; gssize n_read; n_read = g_input_stream_read (self->input_stream, buf, buf_size, NULL, &error); if (n_read < 0) { gtk_media_stream_gerror (GTK_MEDIA_STREAM (self), error); } else if (n_read == 0) { n_read = AVERROR_EOF; } return n_read; } static GdkMemoryFormat memory_format_from_pix_fmt (enum AVPixelFormat pix_fmt) { switch ((int) pix_fmt) { case AV_PIX_FMT_RGBA: return GDK_MEMORY_R8G8B8A8; case AV_PIX_FMT_RGB24: return GDK_MEMORY_R8G8B8; default: g_assert_not_reached (); return GDK_MEMORY_R8G8B8A8; } } static gboolean gtk_ff_media_file_decode_frame (GtkFfMediaFile *self, GtkVideoFrameFFMpeg *result) { GdkTexture *texture; AVPacket packet; AVFrame *frame; int errnum; GBytes *bytes; guchar *data; frame = av_frame_alloc (); for (errnum = av_read_frame (self->format_ctx, &packet); errnum >= 0; errnum = av_read_frame (self->format_ctx, &packet)) { if (self->input_audio_stream && packet.stream_index == self->input_audio_stream->stream_id) { errnum = avcodec_send_packet (self->input_audio_stream->codec_ctx, &packet); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } if (errnum >= 0) { errnum = avcodec_receive_frame (self->input_audio_stream->codec_ctx, self->audio_frame); if (errnum == AVERROR (EAGAIN)) { // Just retry with the next packet errnum = 0; continue; } if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } else { av_packet_unref (&packet); } } gtk_ff_media_file_write_audio_frame(self, self->audio_frame); } else if (self->input_video_stream && packet.stream_index == self->input_video_stream->stream_id) { errnum = avcodec_send_packet (self->input_video_stream->codec_ctx, &packet); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } if (errnum >= 0) { errnum = avcodec_receive_frame (self->input_video_stream->codec_ctx, frame); if (errnum == AVERROR (EAGAIN)) { // Just retry with the next packet errnum = 0; continue; } if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } else { av_packet_unref (&packet); break; } } } av_packet_unref (&packet); } if (errnum < 0) { if (errnum != AVERROR_EOF) gtk_ff_media_file_set_ffmpeg_error (self, errnum); av_frame_free (&frame); return FALSE; } data = g_try_malloc0 (self->input_video_stream->codec_ctx->width * self->input_video_stream->codec_ctx->height * 4); if (data == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_FAILED, _("Not enough memory")); av_frame_free (&frame); return FALSE; } if (self->sws_ctx == NULL || self->sws_pix_fmt != frame->format) { const AVPixFmtDescriptor *desc; enum AVPixelFormat gdk_pix_fmt; g_clear_pointer (&self->sws_ctx, sws_freeContext); self->sws_pix_fmt = frame->format; desc = av_pix_fmt_desc_get (self->sws_pix_fmt); /* Use gdk-pixbuf formats because ffmpeg can't premultiply */ if (desc != NULL && (desc->flags & AV_PIX_FMT_FLAG_ALPHA)) gdk_pix_fmt = AV_PIX_FMT_RGBA; else gdk_pix_fmt = AV_PIX_FMT_RGB24; self->sws_ctx = sws_getContext (self->input_video_stream->codec_ctx->width, self->input_video_stream->codec_ctx->height, frame->format, self->input_video_stream->codec_ctx->width, self->input_video_stream->codec_ctx->height, gdk_pix_fmt, 0, NULL, NULL, NULL); self->memory_format = memory_format_from_pix_fmt (gdk_pix_fmt); } sws_scale(self->sws_ctx, (const uint8_t * const *) frame->data, frame->linesize, 0, self->input_video_stream->codec_ctx->height, (uint8_t *[1]) { data }, (int[1]) { self->input_video_stream->codec_ctx->width * 4 }); bytes = g_bytes_new_take (data, self->input_video_stream->codec_ctx->width * self->input_video_stream->codec_ctx->height * 4); texture = gdk_memory_texture_new (self->input_video_stream->codec_ctx->width, self->input_video_stream->codec_ctx->height, self->memory_format, bytes, self->input_video_stream->codec_ctx->width * 4); g_bytes_unref (bytes); gtk_video_frame_ffmpeg_init (result, texture, av_rescale_q (frame->best_effort_timestamp, self->input_video_stream->stream->time_base, (AVRational) { 1, G_USEC_PER_SEC })); av_frame_free (&frame); return TRUE; } static int64_t gtk_ff_media_file_seek_cb (void *data, int64_t offset, int whence) { GtkFfMediaFile *self = data; GSeekType seek_type; gboolean result; switch (whence) { case SEEK_SET: seek_type = G_SEEK_SET; break; case SEEK_CUR: seek_type = G_SEEK_CUR; break; case SEEK_END: seek_type = G_SEEK_END; break; case AVSEEK_SIZE: /* FIXME: Handle size querying */ return -1; default: g_assert_not_reached (); return -1; } result = g_seekable_seek (G_SEEKABLE (self->input_stream), offset, seek_type, NULL, NULL); if (!result) return -1; return g_seekable_tell (G_SEEKABLE (self->input_stream)); } static gboolean gtk_ff_media_file_create_input_stream (GtkFfMediaFile *self) { GError *error = NULL; GFile *file; file = gtk_media_file_get_file (GTK_MEDIA_FILE (self)); if (file) { self->input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error)); if (self->input_stream == NULL) { gtk_media_stream_gerror (GTK_MEDIA_STREAM (self), error); g_error_free (error); return FALSE; } } else { self->input_stream = g_object_ref (gtk_media_file_get_input_stream (GTK_MEDIA_FILE (self))); } return TRUE; } static AVIOContext * gtk_ff_media_file_create_io_context (GtkFfMediaFile *self) { AVIOContext *result; int buffer_size = 4096; /* it's what everybody else uses... */ unsigned char *buffer; if (!gtk_ff_media_file_create_input_stream (self)) return NULL; buffer = av_malloc (buffer_size); if (buffer == NULL) return NULL; result = avio_alloc_context (buffer, buffer_size, AVIO_FLAG_READ, self, gtk_ff_media_file_read_packet_cb, NULL, G_IS_SEEKABLE (self->input_stream) ? gtk_ff_media_file_seek_cb : NULL); result->buf_ptr = result->buf_end; result->write_flag = 0; return result; } static gboolean gtk_ff_media_file_init_audio_resampler (GtkFfMediaFile *self) { AVCodecContext *in_codec_ctx = self->input_audio_stream->codec_ctx; AVCodecContext *out_codec_ctx = self->output_audio_stream->codec_ctx; int errnum; // create resampler context self->swr_ctx = swr_alloc (); if (!self->swr_ctx) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Could not allocate resampler context")); return FALSE; } // set resampler option av_opt_set_int (self->swr_ctx, "in_channel_count", in_codec_ctx->channels, 0); av_opt_set_int (self->swr_ctx, "in_sample_rate", in_codec_ctx->sample_rate, 0); av_opt_set_sample_fmt (self->swr_ctx, "in_sample_fmt", in_codec_ctx->sample_fmt, 0); av_opt_set_int (self->swr_ctx, "out_channel_count", out_codec_ctx->channels, 0); av_opt_set_int (self->swr_ctx, "out_sample_rate", out_codec_ctx->sample_rate, 0); av_opt_set_sample_fmt (self->swr_ctx, "out_sample_fmt", out_codec_ctx->sample_fmt, 0); // initialize the resampling context errnum = swr_init (self->swr_ctx); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } return TRUE; } static gboolean gtk_ff_media_file_open_audio_device (GtkFfMediaFile *self) { const AVOutputFormat *candidate; int errnum; /* Try finding an audio device that supports setting the volume */ for (candidate = av_output_audio_device_next (NULL); candidate != NULL; candidate = av_output_audio_device_next (candidate)) { if (candidate->control_message) break; } /* fallback to the first format available */ if (candidate == NULL) candidate = av_output_audio_device_next (NULL); if (candidate == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _ ("No audio output found")); return FALSE; } errnum = avformat_alloc_output_context2 (&self->device_ctx, candidate, NULL, NULL); if (errnum != 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return FALSE; } return TRUE; } static gboolean gtk_ff_media_file_play (GtkMediaStream *stream); static void gtk_ff_media_file_open (GtkMediaFile *file) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (file); int errnum; int nb_samples; self->format_ctx = avformat_alloc_context (); self->format_ctx->pb = gtk_ff_media_file_create_io_context (self); if (self->format_ctx->pb == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_FAILED, _("Not enough memory")); return; } errnum = avformat_open_input (&self->format_ctx, NULL, NULL, NULL); if (errnum != 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return; } errnum = avformat_find_stream_info (self->format_ctx, NULL); if (errnum < 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return; } self->input_audio_stream = gtk_ff_media_file_find_input_stream (self, AVMEDIA_TYPE_AUDIO); self->input_video_stream = gtk_ff_media_file_find_input_stream (self, AVMEDIA_TYPE_VIDEO); // open an audio device when we have an audio stream if (self->input_audio_stream && gtk_ff_media_file_open_audio_device (self)) { self->output_audio_stream = gtk_ff_media_file_add_output_stream (self, self->device_ctx, self->device_ctx->oformat->audio_codec); gtk_ff_media_file_init_audio_resampler (self); if (self->output_audio_stream->codec_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) nb_samples = 10000; // just taken from the ffmpeg muxing example else nb_samples = self->output_audio_stream->codec_ctx->frame_size; self->audio_frame = gtk_ff_media_file_alloc_audio_frame (self->output_audio_stream->codec_ctx->sample_fmt, self->output_audio_stream->codec_ctx->channel_layout, self->output_audio_stream->codec_ctx->sample_rate, nb_samples); if (self->audio_frame == NULL) { gtk_media_stream_error (GTK_MEDIA_STREAM (self), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Failed to allocate an audio frame")); return; } errnum = avformat_write_header (self->device_ctx, NULL); if (errnum != 0) { gtk_ff_media_file_set_ffmpeg_error (self, errnum); return; } } gtk_media_stream_stream_prepared (GTK_MEDIA_STREAM (self), self->output_audio_stream != NULL, self->input_video_stream != NULL, TRUE, self->format_ctx->duration != AV_NOPTS_VALUE ? av_rescale (self->format_ctx->duration, G_USEC_PER_SEC, AV_TIME_BASE) : 0); gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); if (gtk_ff_media_file_decode_frame (self, &self->current_frame)) gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); if (gtk_media_stream_get_playing (GTK_MEDIA_STREAM (self))) gtk_ff_media_file_play (GTK_MEDIA_STREAM (self)); } static void gtk_ff_media_file_close (GtkMediaFile *file) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (file); g_clear_object (&self->input_stream); g_clear_pointer (&self->swr_ctx, swr_close); g_clear_pointer (&self->sws_ctx, sws_freeContext); g_clear_pointer (&self->input_audio_stream, gtk_ff_stream_close); g_clear_pointer (&self->input_video_stream, gtk_ff_stream_close); g_clear_pointer (&self->output_audio_stream, gtk_ff_stream_close); av_frame_free (&self->audio_frame); avformat_free_context(self->device_ctx); avformat_close_input (&self->format_ctx); gtk_video_frame_ffmpeg_clear (&self->next_frame); gtk_video_frame_ffmpeg_clear (&self->current_frame); gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); } static gboolean gtk_ff_media_file_next_frame_cb (gpointer data); static void gtk_ff_media_file_queue_frame (GtkFfMediaFile *self) { gint64 time, frame_time; guint delay; time = g_get_monotonic_time (); frame_time = self->start_time + self->next_frame.timestamp; delay = time > frame_time ? 0 : (frame_time - time) / 1000; self->next_frame_cb = g_timeout_add (delay, gtk_ff_media_file_next_frame_cb, self); } static gboolean gtk_ff_media_file_restart (GtkFfMediaFile *self) { if (!gtk_ff_media_file_seek_stream (self, self->input_audio_stream, 0)) return FALSE; if (!gtk_ff_media_file_seek_stream (self, self->input_video_stream, 0)) return FALSE; if (!gtk_ff_media_file_decode_frame (self, &self->next_frame)) return FALSE; return TRUE; } static gboolean gtk_ff_media_file_next_frame_cb (gpointer data) { GtkFfMediaFile *self = data; self->next_frame_cb = 0; if (gtk_video_frame_ffmpeg_is_empty (&self->next_frame)) { if (!gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self)) || !gtk_ff_media_file_restart (self)) { gtk_media_stream_stream_ended (GTK_MEDIA_STREAM (self)); return G_SOURCE_REMOVE; } self->start_time += self->current_frame.timestamp - self->next_frame.timestamp; } gtk_video_frame_ffmpeg_clear (&self->current_frame); gtk_video_frame_ffmpeg_move (&self->current_frame, &self->next_frame); gtk_media_stream_update (GTK_MEDIA_STREAM (self), self->current_frame.timestamp); gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); /* ignore failure here, we'll handle the empty frame case above * the next time we're called. */ gtk_ff_media_file_decode_frame (self, &self->next_frame); gtk_ff_media_file_queue_frame (self); return G_SOURCE_REMOVE; } static gboolean gtk_ff_media_file_play (GtkMediaStream *stream) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (stream); if (self->format_ctx == NULL) return FALSE; if (!gtk_media_stream_is_prepared (stream)) return TRUE; if (gtk_video_frame_ffmpeg_is_empty (&self->next_frame) && !gtk_ff_media_file_decode_frame (self, &self->next_frame)) { if (gtk_ff_media_file_restart (self)) { self->start_time = g_get_monotonic_time () - self->next_frame.timestamp; } else { return FALSE; } } else { self->start_time = g_get_monotonic_time () - self->current_frame.timestamp; } gtk_ff_media_file_queue_frame (self); return TRUE; } static void gtk_ff_media_file_pause (GtkMediaStream *stream) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (stream); if (self->next_frame_cb) { g_source_remove (self->next_frame_cb); self->next_frame_cb = 0; } self->start_time = 0; } static void gtk_ff_media_file_seek (GtkMediaStream *stream, gint64 timestamp) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (stream); if (!gtk_ff_media_file_seek_stream (self, self->input_audio_stream, timestamp)) return; if (!gtk_ff_media_file_seek_stream (self, self->input_video_stream, timestamp)) return; gtk_media_stream_seek_success (stream); gtk_video_frame_ffmpeg_clear (&self->next_frame); gtk_video_frame_ffmpeg_clear (&self->current_frame); if (gtk_ff_media_file_decode_frame (self, &self->current_frame)) gtk_media_stream_update (stream, self->current_frame.timestamp); gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); if (gtk_media_stream_get_playing (stream)) { gtk_ff_media_file_pause (stream); if (!gtk_ff_media_file_play (stream)) gtk_media_stream_stream_ended (stream); } } static void gtk_ff_media_file_update_audio (GtkMediaStream *stream, gboolean muted, double volume) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (stream); int errnum; errnum = avdevice_app_to_dev_control_message (self->device_ctx, muted ? AV_APP_TO_DEV_MUTE : AV_APP_TO_DEV_UNMUTE, NULL, 0); if (errnum < 0) { g_warning ("Cannot set audio mute state"); } errnum = avdevice_app_to_dev_control_message (self->device_ctx, AV_APP_TO_DEV_SET_VOLUME, &volume, sizeof (volume)); if (errnum < 0) { g_warning ("Cannot set audio volume"); } } static void gtk_ff_media_file_dispose (GObject *object) { GtkFfMediaFile *self = GTK_FF_MEDIA_FILE (object); gtk_ff_media_file_pause (GTK_MEDIA_STREAM (self)); gtk_ff_media_file_close (GTK_MEDIA_FILE (self)); G_OBJECT_CLASS (gtk_ff_media_file_parent_class)->dispose (object); } static void gtk_ff_media_file_class_init (GtkFfMediaFileClass *klass) { GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass); GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); file_class->open = gtk_ff_media_file_open; file_class->close = gtk_ff_media_file_close; stream_class->play = gtk_ff_media_file_play; stream_class->pause = gtk_ff_media_file_pause; stream_class->seek = gtk_ff_media_file_seek; stream_class->update_audio = gtk_ff_media_file_update_audio; gobject_class->dispose = gtk_ff_media_file_dispose; } static void gtk_ff_media_file_init (GtkFfMediaFile *self) { }