aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2021-12-06 14:00:27 +0100
committerShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2021-12-28 10:07:31 +0100
commit0c62b3c2fc974d0c652d905a1268f3d0f74a330e (patch)
tree16569edfc094aecb05ad4fedb2ff694c14f728f1 /examples
parentd727a69e2f58be167845b8179edf1735df6ccf1e (diff)
AudioSource Example + Binding for QAudioFormat.normalizedSampleValue(data: bytes)
In order to make the AudioSource example available in Python, the following functions were properly exposed to Python. - normalizedSampleSource(const void* data) const in QAudioFormat Previously, Shiboken took care of exposing these functions on its own. The fix here is to use PyBuffer as the counterpart for const char* in the Python side. The patch also consists of the working AudioSource example. Currently only the Push Mode works. Inorder to make the pull mode work, the function writeData(const char* data, qint64 len) should be properly exposed from C++ to Python through typestem, without code duplication. Task-number: PYSIDE-841 Task-number: PYSIDE-1743 Pick-to: 6.2 Change-Id: I1cd2c28136836c9bdf1021693f74e59eb98f390b Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'examples')
-rw-r--r--examples/multimedia/audiosource/audiosource.py241
-rw-r--r--examples/multimedia/audiosource/audiosource.pyproject3
-rw-r--r--examples/multimedia/audiosource/doc/audiosource.pngbin0 -> 11897 bytes
-rw-r--r--examples/multimedia/audiosource/doc/audiosource.rst12
4 files changed, 256 insertions, 0 deletions
diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py
new file mode 100644
index 000000000..d61aaae7e
--- /dev/null
+++ b/examples/multimedia/audiosource/audiosource.py
@@ -0,0 +1,241 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""
+PySide6 port of Qt6 example examples/multimedia/audiosources
+
+Audio Devices demonstrates how to create a simple application to list and test
+the configuration for the various audio devices available on the target device
+or desktop PC.
+
+Note: This Python example is not fully complete as compared to its C++ counterpart.
+Only the push mode works at the moment. For the pull mode to work, the class
+QIODevice have python bindings that needs to be fixed.
+"""
+import sys
+from typing import Optional
+
+import PySide6
+from PySide6.QtCore import QByteArray, QIODevice, QMargins, QRect, Qt, Signal, Slot
+from PySide6.QtGui import QPainter, QPalette
+from PySide6.QtMultimedia import (
+ QAudio,
+ QAudioDevice,
+ QAudioFormat,
+ QAudioSource,
+ QMediaDevices,
+)
+from PySide6.QtWidgets import (
+ QApplication,
+ QComboBox,
+ QPushButton,
+ QSlider,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+class AudioInfo:
+ def __init__(self, format: QAudioFormat):
+ super().__init__()
+ self.m_format = format
+ self.m_level = 0.0
+
+ def calculate_level(self, data: bytes, length: int) -> float:
+ channel_bytes: int = int(self.m_format.bytesPerSample())
+ sample_bytes: int = int(self.m_format.bytesPerFrame())
+ num_samples: int = int(length / sample_bytes)
+
+ maxValue: float = 0
+ m_offset: int = 0
+
+ for i in range(num_samples):
+ for j in range(self.m_format.channelCount()):
+ value = 0
+ if len(data) > m_offset:
+ data_sample = data[m_offset:]
+ value = self.m_format.normalizedSampleValue(data_sample)
+ maxValue = max(value, maxValue)
+ m_offset = m_offset + channel_bytes
+
+ return maxValue
+
+
+class RenderArea(QWidget):
+ def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None:
+ super().__init__(parent=parent)
+ self.m_level = 0
+ self.setBackgroundRole(QPalette.Base)
+ self.setAutoFillBackground(True)
+ self.setMinimumHeight(30)
+ self.setMinimumWidth(200)
+
+ def set_level(self, value):
+ self.m_level = value
+ self.update()
+
+ def paintEvent(self, event: PySide6.QtGui.QPaintEvent) -> None:
+ painter = QPainter(self)
+ painter.setPen(Qt.black)
+ frame = painter.viewport() - QMargins(10, 10, 10, 10)
+
+ painter.drawRect(frame)
+
+ if self.m_level == 0.0:
+ return
+
+ pos: int = round((frame.width() - 1) * self.m_level)
+ painter.fillRect(
+ frame.left() + 1, frame.top() + 1, pos, frame.height() - 1, Qt.red
+ )
+
+
+class InputTest(QWidget):
+ def __init__(self) -> None:
+ super().__init__()
+ self.m_devices = QMediaDevices(self)
+ self.m_pullMode = False
+
+ self.initialize_window()
+ self.initialize_audio(QMediaDevices.defaultAudioInput())
+
+ def initialize_window(self):
+ self.layout = QVBoxLayout(self)
+
+ self.m_canvas = RenderArea(self)
+ self.layout.addWidget(self.m_canvas)
+
+ self.m_device_box = QComboBox(self)
+ default_device_info = QMediaDevices.defaultAudioInput()
+ self.m_device_box.addItem(
+ default_device_info.description(), default_device_info
+ )
+
+ for device_info in self.m_devices.audioInputs():
+ if device_info != default_device_info:
+ self.m_device_box.addItem(device_info.description(), device_info)
+
+ self.m_device_box.activated[int].connect(self.device_changed)
+ self.layout.addWidget(self.m_device_box)
+
+ self.m_volume_slider = QSlider(Qt.Horizontal, self)
+ self.m_volume_slider.setRange(0, 100)
+ self.m_volume_slider.setValue(100)
+ self.m_volume_slider.valueChanged.connect(self.slider_changed)
+ self.layout.addWidget(self.m_volume_slider)
+
+ self.m_mode_button = QPushButton(self)
+ self.m_mode_button.clicked.connect(self.toggle_mode)
+ self.layout.addWidget(self.m_mode_button)
+
+ self.m_suspend_resume_button = QPushButton(self)
+ self.m_suspend_resume_button.clicked.connect(self.toggle_suspend)
+ self.layout.addWidget(self.m_suspend_resume_button)
+
+ def initialize_audio(self, device_info: QAudioDevice):
+ format = QAudioFormat()
+ format.setSampleRate(8000)
+ format.setChannelCount(1)
+ format.setSampleFormat(QAudioFormat.Int16)
+
+ self.m_audio_info = AudioInfo(format)
+
+ self.m_audio_input = QAudioSource(device_info, format)
+ initial_volume = QAudio.convertVolume(
+ self.m_audio_input.volume(),
+ QAudio.LinearVolumeScale,
+ QAudio.LogarithmicVolumeScale,
+ )
+ self.m_volume_slider.setValue(int(round(initial_volume * 100)))
+ self.toggle_mode()
+
+ @Slot()
+ def toggle_mode(self):
+ self.m_audio_input.stop()
+ self.toggle_suspend()
+
+ self.m_mode_button.setText("Enable pull mode")
+ io = self.m_audio_input.start()
+
+ def push_mode_slot():
+ len = self.m_audio_input.bytesAvailable()
+ buffer_size = 4096
+ if len > buffer_size:
+ len = buffer_size
+ buffer: QByteArray = io.read(len)
+ if len > 0:
+ level = self.m_audio_info.calculate_level(buffer, len)
+ self.m_canvas.set_level(level)
+
+ io.readyRead.connect(push_mode_slot)
+
+ @Slot()
+ def toggle_suspend(self):
+ # toggle suspend/resume
+ state = self.m_audio_input.state()
+ if (state == QAudio.SuspendedState) or (state == QAudio.StoppedState):
+ self.m_audio_input.resume()
+ self.m_suspend_resume_button.setText("Suspend recording")
+ elif state == QAudio.ActiveState:
+ self.m_audio_input.suspend()
+ self.m_suspend_resume_button.setText("Resume recording")
+ # else no-op
+
+ @Slot(int)
+ def device_changed(self, index):
+ self.m_audio_input.stop()
+ self.m_audio_input.disconnect(self)
+ self.initialize_audio(self.m_device_box.itemData(index))
+
+ @Slot(int)
+ def slider_changed(self, value):
+ linearVolume = QAudio.convertVolume(
+ value / float(100), QAudio.LogarithmicVolumeScale, QAudio.LinearVolumeScale
+ )
+
+ self.m_audio_input.setVolume(linearVolume)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ app.setApplicationName("Audio Sources Example")
+ input = InputTest()
+ input.show()
+ sys.exit(app.exec())
diff --git a/examples/multimedia/audiosource/audiosource.pyproject b/examples/multimedia/audiosource/audiosource.pyproject
new file mode 100644
index 000000000..c09e77303
--- /dev/null
+++ b/examples/multimedia/audiosource/audiosource.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["audiosource.py"]
+}
diff --git a/examples/multimedia/audiosource/doc/audiosource.png b/examples/multimedia/audiosource/doc/audiosource.png
new file mode 100644
index 000000000..cac183b75
--- /dev/null
+++ b/examples/multimedia/audiosource/doc/audiosource.png
Binary files differ
diff --git a/examples/multimedia/audiosource/doc/audiosource.rst b/examples/multimedia/audiosource/doc/audiosource.rst
new file mode 100644
index 000000000..0a2353bc5
--- /dev/null
+++ b/examples/multimedia/audiosource/doc/audiosource.rst
@@ -0,0 +1,12 @@
+Audio Source Example
+====================
+
+A Python application that demonstrates the analogous example in C++ `Audio
+Source Example
+https://doc-snapshots.qt.io/qt6-dev/qtmultimedia-multimedia-audiosource-example.html`_
+
+
+.. image:: audiosource.png
+ :width: 400
+ :alt: audiosource example
+