/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "qgstappsrc_p.h" #include "qgstutils_p.h" #include "qnetworkreply.h" #include "qloggingcategory.h" Q_LOGGING_CATEGORY(qLcAppSrc, "qt.multimedia.appsrc") QGstAppSrc::QGstAppSrc(QObject *parent) : QObject(parent) { m_appSrc = QGstElement("appsrc", "appsrc"); if (m_appSrc.isNull()) qWarning() << "Could not create GstAppSrc."; } QGstAppSrc::~QGstAppSrc() { m_appSrc.setStateSync(GST_STATE_NULL); streamDestroyed(); qCDebug(qLcAppSrc) << "~QGstAppSrc"; } bool QGstAppSrc::setup(QIODevice *stream, qint64 offset) { if (m_appSrc.isNull()) return false; if (!setStream(stream, offset)) return false; auto *appSrc = GST_APP_SRC(m_appSrc.element()); GstAppSrcCallbacks m_callbacks; memset(&m_callbacks, 0, sizeof(GstAppSrcCallbacks)); m_callbacks.need_data = &QGstAppSrc::on_need_data; m_callbacks.enough_data = &QGstAppSrc::on_enough_data; m_callbacks.seek_data = &QGstAppSrc::on_seek_data; gst_app_src_set_callbacks(appSrc, (GstAppSrcCallbacks*)&m_callbacks, this, nullptr); m_maxBytes = gst_app_src_get_max_bytes(appSrc); m_suspended = false; if (m_sequential) m_streamType = GST_APP_STREAM_TYPE_STREAM; else m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; gst_app_src_set_stream_type(appSrc, m_streamType); gst_app_src_set_size(appSrc, m_sequential ? -1 : m_stream->size() - m_offset); m_networkReply = qobject_cast(m_stream); m_noMoreData = true; return true; } void QGstAppSrc::setAudioFormat(const QAudioFormat &f) { m_format = f; if (!m_format.isValid()) return; auto caps = QGstUtils::capsForAudioFormat(m_format); Q_ASSERT(!caps.isNull()); m_appSrc.set("caps", caps); m_appSrc.set("format", GST_FORMAT_TIME); } void QGstAppSrc::setExternalAppSrc(const QGstElement &appsrc) { m_appSrc = appsrc; } bool QGstAppSrc::setStream(QIODevice *stream, qint64 offset) { if (m_stream) { disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); disconnect(m_stream, SIGNAL(destroyed()), this, SLOT(streamDestroyed())); m_stream = nullptr; } m_dataRequestSize = 0; m_sequential = true; m_maxBytes = 0; streamedSamples = 0; if (stream) { if (!stream->isOpen() && !stream->open(QIODevice::ReadOnly)) return false; m_stream = stream; connect(m_stream, SIGNAL(destroyed()), SLOT(streamDestroyed())); connect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); m_sequential = m_stream->isSequential(); m_offset = offset; } return true; } QGstElement QGstAppSrc::element() { return m_appSrc; } void QGstAppSrc::write(const char *data, qsizetype size) { qCDebug(qLcAppSrc) << "write" << size << m_noMoreData << m_dataRequestSize; if (!size) return; Q_ASSERT(!m_stream); m_buffer.append(data, size); m_noMoreData = false; pushData(); } void QGstAppSrc::onDataReady() { qCDebug(qLcAppSrc) << "onDataReady" << m_stream->bytesAvailable() << m_stream->size(); pushData(); } void QGstAppSrc::streamDestroyed() { qCDebug(qLcAppSrc) << "stream destroyed"; m_stream = nullptr; m_dataRequestSize = 0; streamedSamples = 0; sendEOS(); } void QGstAppSrc::pushData() { if (m_appSrc.isNull() || !m_dataRequestSize || m_suspended) { qCDebug(qLcAppSrc) << "push data: return immediately" << m_appSrc.isNull() << m_dataRequestSize << m_suspended; return; } qCDebug(qLcAppSrc) << "pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); if ((m_stream && m_stream->atEnd())) { eosOrIdle(); qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); return; } qint64 size; if (m_stream) size = m_stream->bytesAvailable(); else size = m_buffer.size(); if (!m_dataRequestSize) m_dataRequestSize = m_maxBytes; size = qMin(size, (qint64)m_dataRequestSize); qCDebug(qLcAppSrc) << " reading" << size << "bytes" << size << m_dataRequestSize; GstBuffer* buffer = gst_buffer_new_and_alloc(size); if (m_sequential || !m_stream) buffer->offset = bytesReadSoFar; else buffer->offset = m_stream->pos(); if (m_format.isValid()) { // timestamp raw audio data uint nSamples = size/m_format.bytesPerFrame(); GST_BUFFER_TIMESTAMP(buffer) = gst_util_uint64_scale(streamedSamples, GST_SECOND, m_format.sampleRate()); GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale(nSamples, GST_SECOND, m_format.sampleRate()); streamedSamples += nSamples; } GstMapInfo mapInfo; gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE); void* bufferData = mapInfo.data; qint64 bytesRead; if (m_stream) bytesRead = m_stream->read((char*)bufferData, size); else bytesRead = m_buffer.read((char*)bufferData, size); buffer->offset_end = buffer->offset + bytesRead - 1; bytesReadSoFar += bytesRead; gst_buffer_unmap(buffer, &mapInfo); qCDebug(qLcAppSrc) << "pushing bytes into gstreamer" << buffer->offset << bytesRead; if (bytesRead == 0) { gst_buffer_unref(buffer); eosOrIdle(); qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); return; } m_noMoreData = false; emit bytesProcessed(bytesRead); GstFlowReturn ret = gst_app_src_push_buffer(GST_APP_SRC(m_appSrc.element()), buffer); if (ret == GST_FLOW_ERROR) { qWarning() << "QGstAppSrc: push buffer error"; } else if (ret == GST_FLOW_FLUSHING) { qWarning() << "QGstAppSrc: push buffer wrong state"; } qCDebug(qLcAppSrc) << "end pushData" << (m_stream ? m_stream : nullptr) << m_buffer.size(); } bool QGstAppSrc::doSeek(qint64 value) { if (isStreamValid()) return m_stream->seek(value + m_offset); return false; } gboolean QGstAppSrc::on_seek_data(GstAppSrc *, guint64 arg0, gpointer userdata) { // we do get some spurious seeks to INT_MAX, ignore those if (arg0 == std::numeric_limits::max()) return true; QGstAppSrc *self = reinterpret_cast(userdata); Q_ASSERT(self); if (self->m_sequential) return false; QMetaObject::invokeMethod(self, "doSeek", Qt::AutoConnection, Q_ARG(qint64, arg0)); return true; } void QGstAppSrc::on_enough_data(GstAppSrc *, gpointer userdata) { qCDebug(qLcAppSrc) << "on_enough_data"; QGstAppSrc *self = static_cast(userdata); Q_ASSERT(self); self->m_dataRequestSize = 0; } void QGstAppSrc::on_need_data(GstAppSrc *, guint arg0, gpointer userdata) { qCDebug(qLcAppSrc) << "on_need_data requesting bytes" << arg0; QGstAppSrc *self = static_cast(userdata); Q_ASSERT(self); self->m_dataRequestSize = arg0; QMetaObject::invokeMethod(self, "pushData", Qt::AutoConnection); qCDebug(qLcAppSrc) << "done on_need_data"; } void QGstAppSrc::sendEOS() { qCDebug(qLcAppSrc) << "sending EOS"; if (m_appSrc.isNull()) return; gst_app_src_end_of_stream(GST_APP_SRC(m_appSrc.element())); } void QGstAppSrc::eosOrIdle() { qCDebug(qLcAppSrc) << "eosOrIdle"; if (m_appSrc.isNull()) return; if (!m_sequential) { sendEOS(); return; } if (m_noMoreData) return; qCDebug(qLcAppSrc) << " idle!"; m_noMoreData = true; emit noMoreData(); }