gstreamer/subprojects/gst-examples/playback/player/qt/extension/qgstplayer.cpp

954 lines
21 KiB
C++
Raw Normal View History

/* GStreamer
*
* Copyright (C) 2015 Alexandre Moreno <alexmorenocano@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "qgstplayer.h"
#include <QDebug>
#include <QSize>
#include <QMetaObject>
#include <functional>
#include <QThread>
#include <QtAlgorithms>
#include <QImage>
#include <gst/gst.h>
#include <gst/tag/tag.h>
namespace QGstPlayer {
class RegisterMetaTypes
{
public:
RegisterMetaTypes()
{
qRegisterMetaType<Player::State>("State");
}
} _register;
MediaInfo::MediaInfo(Player *player)
: QObject(player)
, uri_()
, title_()
, isSeekable_(false)
, videoStreams_()
, audioStreams_()
, subtitleStreams_()
, sample_()
{
}
QString MediaInfo::uri() const
{
return uri_;
}
QString MediaInfo::title() const
{
return title_;
}
bool MediaInfo::isSeekable() const
{
return isSeekable_;
}
const QList<QObject *> &MediaInfo::videoStreams() const
{
return videoStreams_;
}
const QList<QObject *> &MediaInfo::audioStreams() const
{
return audioStreams_;
}
const QList<QObject*> &MediaInfo::subtitleStreams() const
{
return subtitleStreams_;
}
const QImage &MediaInfo::sample()
{
return sample_;
}
void MediaInfo::update(GstPlayerMediaInfo *info)
{
Q_ASSERT(info != 0);
// FIXME since media-info signal gets emitted
// several times, we just update info iff the
// media uri has changed.
if (uri_ == gst_player_media_info_get_uri(info)) {
return;
}
uri_ = QString(gst_player_media_info_get_uri(info));
title_ = QString::fromLocal8Bit(gst_player_media_info_get_title(info));
// if media has no title, return the file name
if (title_.isEmpty()) {
QUrl url(gst_player_media_info_get_uri(info));
title_ = url.fileName();
}
emit titleChanged();
if (isSeekable_ != gst_player_media_info_is_seekable(info)) {
isSeekable_ = !isSeekable_;
emit seekableChanged();
}
if (!subtitleStreams_.isEmpty()) {
qDeleteAll(subtitleStreams_);
subtitleStreams_.clear();
}
if (!videoStreams_.isEmpty()) {
qDeleteAll(videoStreams_);
videoStreams_.clear();
}
if (!audioStreams_.isEmpty()) {
qDeleteAll(audioStreams_);
audioStreams_.clear();
}
GList *list = gst_player_get_subtitle_streams(info);
g_list_foreach (list, [](gpointer data, gpointer user_data) {
GstPlayerSubtitleInfo *info = static_cast<GstPlayerSubtitleInfo*>(data);
QList<QObject*> *subs = static_cast<QList<QObject*>*>(user_data);
subs->append(new SubtitleInfo(info));
}, &subtitleStreams_);
list = gst_player_get_video_streams(info);
g_list_foreach (list, [](gpointer data, gpointer user_data) {
GstPlayerVideoInfo *info = static_cast<GstPlayerVideoInfo*>(data);
QList<QObject*> *videos = static_cast<QList<QObject*>*>(user_data);
videos->append(new VideoInfo(info));
}, &videoStreams_);
list = gst_player_get_audio_streams(info);
g_list_foreach (list, [](gpointer data, gpointer user_data) {
GstPlayerAudioInfo *info = static_cast<GstPlayerAudioInfo*>(data);
QList<QObject*> *audios = static_cast<QList<QObject*>*>(user_data);
audios->append(new AudioInfo(info));
}, &audioStreams_);
GstSample *sample;
GstMapInfo map_info;
GstBuffer *buffer;
const GstStructure *caps_struct;
GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED;
/* get image sample buffer from media */
sample = gst_player_media_info_get_image_sample (info);
if (!sample)
return;
buffer = gst_sample_get_buffer (sample);
caps_struct = gst_sample_get_info (sample);
/* if sample is retrieved from preview-image tag then caps struct
* will not be defined. */
if (caps_struct)
gst_structure_get_enum (caps_struct, "image-type",
GST_TYPE_TAG_IMAGE_TYPE, reinterpret_cast<gint*>(&type));
/* FIXME: Should we check more type ?? */
if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) &&
(type != GST_TAG_IMAGE_TYPE_UNDEFINED) &&
(type != GST_TAG_IMAGE_TYPE_NONE)) {
gst_print ("unsupport type ... %d \n", type);
return;
}
if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ)) {
gst_print ("failed to map gst buffer \n");
return;
}
sample_ = QImage::fromData(map_info.data, map_info.size);
if (sample_.isNull())
qWarning() << "failed to load media info sample image";
emit sampleChanged();
gst_buffer_unmap (buffer, &map_info);
}
VideoInfo::VideoInfo(GstPlayerVideoInfo *info)
: StreamInfo(reinterpret_cast<GstPlayerStreamInfo*>(info))
, video_(info)
, resolution_(gst_player_video_info_get_width(info), gst_player_video_info_get_height(info))
{
}
QSize VideoInfo::resolution() const
{
return resolution_;
}
AudioInfo::AudioInfo(GstPlayerAudioInfo *info)
: StreamInfo(reinterpret_cast<GstPlayerStreamInfo*>(info))
, audio_(info)
, language_(gst_player_audio_info_get_language(info))
, channels_(gst_player_audio_info_get_channels(info))
, bitRate_(gst_player_audio_info_get_bitrate(info))
, sampleRate_(gst_player_audio_info_get_sample_rate(info))
{
}
const QString &AudioInfo::language() const
{
return language_;
}
int AudioInfo::channels() const
{
return channels_;
}
int AudioInfo::bitRate() const
{
return bitRate_;
}
int AudioInfo::sampleRate() const
{
return sampleRate_;
}
SubtitleInfo::SubtitleInfo(GstPlayerSubtitleInfo *info)
: StreamInfo(reinterpret_cast<GstPlayerStreamInfo*>(info))
, subtitle_(info)
, language_(gst_player_subtitle_info_get_language(info))
{
}
const QString &SubtitleInfo::language() const
{
return language_;
}
int StreamInfo::index() const
{
return index_;
}
StreamInfo::StreamInfo(GstPlayerStreamInfo *info)
: stream_(info)
, index_(gst_player_stream_info_get_index(info))
{
}
bool Player::isVideoAvailable() const
{
GstPlayerVideoInfo *video_info;
video_info = gst_player_get_current_video_track (player_);
if (video_info) {
g_object_unref (video_info);
return true;
}
return false;
}
MediaInfo *Player::mediaInfo() const
{
return mediaInfo_;
}
QVariant Player::currentVideo() const
{
Q_ASSERT(player_ != 0);
GstPlayerVideoInfo *track = gst_player_get_current_video_track(player_);
if (!track)
return QVariant();
return QVariant::fromValue(new VideoInfo(track));
}
QVariant Player::currentAudio() const
{
Q_ASSERT(player_ != 0);
GstPlayerAudioInfo *track = gst_player_get_current_audio_track(player_);
if (!track)
return QVariant();
return QVariant::fromValue(new AudioInfo(track));
}
QVariant Player::currentSubtitle() const
{
Q_ASSERT(player_ != 0);
GstPlayerSubtitleInfo *track = gst_player_get_current_subtitle_track(player_);
if (!track)
return QVariant();
return QVariant::fromValue(new SubtitleInfo(track));
}
void Player::setCurrentVideo(QVariant track)
{
Q_ASSERT(player_ != 0);
VideoInfo* info = track.value<VideoInfo*>();
Q_ASSERT(info);
gst_player_set_video_track(player_, info->index());
}
void Player::setCurrentAudio(QVariant track)
{
Q_ASSERT(player_ != 0);
AudioInfo* info = track.value<AudioInfo*>();
Q_ASSERT(info);
gst_player_set_audio_track(player_, info->index());
}
void Player::setCurrentSubtitle(QVariant track)
{
Q_ASSERT(player_ != 0);
SubtitleInfo* info = track.value<SubtitleInfo*>();
Q_ASSERT(info);
gst_player_set_subtitle_track(player_, info->index());
}
bool Player::isSubtitleEnabled() const
{
return subtitleEnabled_;
}
void Player::setSubtitleEnabled(bool enabled)
{
Q_ASSERT(player_ != 0);
subtitleEnabled_ = enabled;
gst_player_set_subtitle_track_enabled(player_, enabled);
emit subtitleEnabledChanged(enabled);
}
Player::Player(QObject *parent, VideoRenderer *renderer)
: QObject(parent)
, player_()
, state_(STOPPED)
, videoDimensions_(QSize())
, mediaInfo_()
, videoAvailable_(false)
, subtitleEnabled_(false)
, autoPlay_(false)
{
player_ = gst_player_new(renderer ? renderer->renderer() : 0,
gst_player_qt_signal_dispatcher_new(this));
g_object_connect(player_,
"swapped-signal::state-changed", G_CALLBACK (Player::onStateChanged), this,
"swapped-signal::position-updated", G_CALLBACK (Player::onPositionUpdated), this,
"swapped-signal::duration-changed", G_CALLBACK (Player::onDurationChanged), this,
"swapped-signal::buffering", G_CALLBACK (Player::onBufferingChanged), this,
"swapped-signal::video-dimensions-changed", G_CALLBACK (Player::onVideoDimensionsChanged), this,
"swapped-signal::volume-changed", G_CALLBACK (Player::onVolumeChanged), this,
"swapped-signal::mute-changed", G_CALLBACK (Player::onMuteChanged), this,
"swapped-signal::media-info-updated", G_CALLBACK (Player::onMediaInfoUpdated), this,
"swapped-signal::end-of-stream", G_CALLBACK (Player::onEndOfStreamReached), this, NULL);
mediaInfo_ = new MediaInfo(this);
gst_player_set_subtitle_track_enabled(player_, false);
}
Player::~Player()
{
if (player_) {
g_signal_handlers_disconnect_by_data(player_, this);
gst_player_stop(player_);
g_object_unref(player_);
}
}
void
Player::onStateChanged(Player * player, GstPlayerState state)
{
player->state_ = static_cast<Player::State>(state);
emit player->stateChanged(player->state_);
}
void
Player::onPositionUpdated(Player * player, GstClockTime position)
{
emit player->positionUpdated(position);
}
void
Player::onDurationChanged(Player * player, GstClockTime duration)
{
emit player->durationChanged(duration);
}
void
Player::onBufferingChanged(Player * player, int percent)
{
emit player->bufferingChanged(percent);
}
void
Player::onVideoDimensionsChanged(Player * player, int w, int h)
{
QSize res(w,h);
if (res == player->videoDimensions_)
return;
player->videoDimensions_ = res;
player->setResolution(res);
emit player->resolutionChanged(res);
}
void
Player::onVolumeChanged(Player *player)
{
qreal new_val;
new_val = gst_player_get_volume (player->player_);
emit player->volumeChanged(new_val);
}
void
Player::onMuteChanged(Player *player)
{
bool new_val;
new_val = gst_player_get_mute (player->player_);
emit player->mutedChanged(new_val);
}
void
Player::onMediaInfoUpdated(Player *player, GstPlayerMediaInfo *media_info)
{
bool val = player->isVideoAvailable();
if (player->videoAvailable_ != val) {
player->videoAvailable_ = val;
emit player->videoAvailableChanged(val);
}
player->mediaInfo()->update(media_info);
emit player->mediaInfoChanged();
}
void Player::onEndOfStreamReached(Player *player)
{
Q_ASSERT(player != 0);
emit player->endOfStream();
}
void Player::setUri(QUrl url)
{
Q_ASSERT(player_ != 0);
QByteArray uri = url.toEncoded();
gst_player_set_uri(player_, uri.data());
autoPlay_ ? play() : pause();
emit sourceChanged(url);
}
QList<QUrl> Player::playlist() const
{
return playlist_;
}
void Player::setPlaylist(const QList<QUrl> &playlist)
{
if (!playlist_.isEmpty()) {
playlist_.erase(playlist_.begin(), playlist_.end());
}
playlist_ = playlist;
iter_ = playlist_.begin();
setUri(*iter_);
}
void Player::next()
{
if (playlist_.isEmpty())
return;
if (iter_ == playlist_.end())
return;
setUri(*++iter_);
}
void Player::previous()
{
if (playlist_.isEmpty())
return;
if (iter_ == playlist_.begin())
return;
setUri(*--iter_);
}
bool Player::autoPlay() const
{
return autoPlay_;
}
void Player::setAutoPlay(bool auto_play)
{
autoPlay_ = auto_play;
if (autoPlay_) {
connect(this, SIGNAL(endOfStream()), SLOT(next()));
}
}
QUrl Player::source() const
{
Q_ASSERT(player_ != 0);
QString url = QString::fromLocal8Bit(gst_player_get_uri(player_));
return QUrl(url);
}
qint64 Player::duration() const
{
Q_ASSERT(player_ != 0);
return gst_player_get_duration(player_);
}
qint64 Player::position() const
{
Q_ASSERT(player_ != 0);
return gst_player_get_position(player_);
}
qreal Player::volume() const
{
Q_ASSERT(player_ != 0);
return gst_player_get_volume(player_);
}
bool Player::isMuted() const
{
Q_ASSERT(player_ != 0);
return gst_player_get_mute(player_);
}
int Player::buffering() const
{
return 0;
}
QSize Player::resolution() const
{
return videoDimensions_;
}
void Player::setResolution(QSize size)
{
videoDimensions_ = size;
}
Player::State Player::state() const
{
return state_;
}
GstElement *Player::pipeline() const
{
Q_ASSERT(player_ != 0);
return gst_player_get_pipeline(player_);
}
void Player::play()
{
Q_ASSERT(player_ != 0);
gst_player_play(player_);
}
void Player::pause()
{
Q_ASSERT(player_ != 0);
gst_player_pause(player_);
}
void Player::stop()
{
Q_ASSERT(player_ != 0);
gst_player_stop(player_);
}
void Player::seek(qint64 position)
{
Q_ASSERT(player_ != 0);
gst_player_seek(player_, position);
}
void Player::setSource(QUrl const& url)
{
Q_ASSERT(player_ != 0);
// discard playlist
if (!playlist_.isEmpty()) {
playlist_.erase(playlist_.begin(), playlist_.end());
}
setUri(url);
emit sourceChanged(url);
}
void Player::setVolume(qreal val)
{
Q_ASSERT(player_ != 0);
gst_player_set_volume(player_, val);
}
void Player::setMuted(bool val)
{
Q_ASSERT(player_ != 0);
gst_player_set_mute(player_, val);
}
void Player::setPosition(qint64 pos)
{
Q_ASSERT(player_ != 0);
gst_player_seek(player_, pos);
}
GstPlayerVideoRenderer *VideoRenderer::renderer()
{
return static_cast<GstPlayerVideoRenderer*> (gst_object_ref (renderer_));
}
VideoRenderer::VideoRenderer()
{
renderer_ = static_cast<GstPlayerVideoRenderer*>
(g_object_new (GST_TYPE_PLAYER_QT_VIDEO_RENDERER, "renderer", this, NULL));
}
VideoRenderer::~VideoRenderer()
{
if (renderer_) gst_object_unref(renderer_);
}
}
struct _GstPlayerQtVideoRenderer
{
GObject parent;
gpointer renderer;
};
struct _GstPlayerQtVideoRendererClass
{
GObjectClass parent_class;
};
static void
gst_player_qt_video_renderer_interface_init
(GstPlayerVideoRendererInterface * iface);
G_DEFINE_TYPE_WITH_CODE (GstPlayerQtVideoRenderer,
gst_player_qt_video_renderer, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER,
gst_player_qt_video_renderer_interface_init))
enum
{
QT_VIDEO_RENDERER_PROP_0,
QT_VIDEO_RENDERER_PROP_RENDERER,
QT_VIDEO_RENDERER_PROP_LAST
};
static GParamSpec * qt_video_renderer_param_specs
[QT_VIDEO_RENDERER_PROP_LAST] = { NULL, };
static void
gst_player_qt_video_renderer_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object);
switch (prop_id) {
case QT_VIDEO_RENDERER_PROP_RENDERER:
self->renderer = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_player_qt_video_renderer_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object);
switch (prop_id) {
case QT_VIDEO_RENDERER_PROP_RENDERER:
g_value_set_pointer (value, self->renderer);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_player_qt_video_renderer_finalize (GObject * object)
{
G_OBJECT_CLASS
(gst_player_qt_video_renderer_parent_class)->finalize(object);
}
static void
gst_player_qt_video_renderer_class_init
(GstPlayerQtVideoRendererClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property =
gst_player_qt_video_renderer_set_property;
gobject_class->get_property =
gst_player_qt_video_renderer_get_property;
gobject_class->finalize = gst_player_qt_video_renderer_finalize;
qt_video_renderer_param_specs
[QT_VIDEO_RENDERER_PROP_RENDERER] =
g_param_spec_pointer ("renderer", "Qt Renderer", "",
static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (gobject_class,
QT_VIDEO_RENDERER_PROP_LAST,
qt_video_renderer_param_specs);
}
static void
gst_player_qt_video_renderer_init (G_GNUC_UNUSED GstPlayerQtVideoRenderer * self)
{
}
static GstElement *
gst_player_qt_video_renderer_create_video_sink
(GstPlayerVideoRenderer * iface, G_GNUC_UNUSED GstPlayer *player)
{
GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (iface);
g_assert(self->renderer != NULL);
return static_cast<QGstPlayer::VideoRenderer*>(self->renderer)->createVideoSink();
}
static void
gst_player_qt_video_renderer_interface_init
(GstPlayerVideoRendererInterface * iface)
{
iface->create_video_sink = gst_player_qt_video_renderer_create_video_sink;
}
struct _GstPlayerQtSignalDispatcher
{
GObject parent;
gpointer player;
};
struct _GstPlayerQtSignalDispatcherClass
{
GObjectClass parent_class;
};
static void
gst_player_qt_signal_dispatcher_interface_init
(GstPlayerSignalDispatcherInterface * iface);
enum
{
QT_SIGNAL_DISPATCHER_PROP_0,
QT_SIGNAL_DISPATCHER_PROP_PLAYER,
QT_SIGNAL_DISPATCHER_PROP_LAST
};
G_DEFINE_TYPE_WITH_CODE (GstPlayerQtSignalDispatcher,
gst_player_qt_signal_dispatcher, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_SIGNAL_DISPATCHER,
gst_player_qt_signal_dispatcher_interface_init));
static GParamSpec
* qt_signal_dispatcher_param_specs
[QT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, };
static void
gst_player_qt_signal_dispatcher_finalize (GObject * object)
{
G_OBJECT_CLASS
(gst_player_qt_signal_dispatcher_parent_class)->finalize
(object);
}
static void
gst_player_qt_signal_dispatcher_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstPlayerQtSignalDispatcher *self =
GST_PLAYER_QT_SIGNAL_DISPATCHER (object);
switch (prop_id) {
case QT_SIGNAL_DISPATCHER_PROP_PLAYER:
self->player = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_player_qt_signal_dispatcher_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstPlayerQtSignalDispatcher *self =
GST_PLAYER_QT_SIGNAL_DISPATCHER (object);
switch (prop_id) {
case QT_SIGNAL_DISPATCHER_PROP_PLAYER:
g_value_set_pointer (value, self->player);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_player_qt_signal_dispatcher_class_init
(GstPlayerQtSignalDispatcherClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize =
gst_player_qt_signal_dispatcher_finalize;
gobject_class->set_property =
gst_player_qt_signal_dispatcher_set_property;
gobject_class->get_property =
gst_player_qt_signal_dispatcher_get_property;
qt_signal_dispatcher_param_specs
[QT_SIGNAL_DISPATCHER_PROP_PLAYER] =
g_param_spec_pointer ("player", "Player instance", "",
static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (gobject_class,
QT_SIGNAL_DISPATCHER_PROP_LAST,
qt_signal_dispatcher_param_specs);
}
static void
gst_player_qt_signal_dispatcher_init
(G_GNUC_UNUSED GstPlayerQtSignalDispatcher * self)
{
}
static void
gst_player_qt_signal_dispatcher_dispatch (GstPlayerSignalDispatcher
* iface, G_GNUC_UNUSED GstPlayer * player, void (*emitter) (gpointer data), gpointer data,
GDestroyNotify destroy)
{
GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (iface);
QObject dispatch;
QObject *receiver = static_cast<QObject*>(self->player);
QObject::connect(&dispatch, &QObject::destroyed, receiver, [=]() {
emitter(data);
if (destroy) destroy(data);
}, Qt::QueuedConnection);
}
static void
gst_player_qt_signal_dispatcher_interface_init
(GstPlayerSignalDispatcherInterface * iface)
{
iface->dispatch = gst_player_qt_signal_dispatcher_dispatch;
}
GstPlayerSignalDispatcher *
gst_player_qt_signal_dispatcher_new (gpointer player)
{
return static_cast<GstPlayerSignalDispatcher*>
(g_object_new (GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER,
"player", player, NULL));
}