Skip to content

Commit

Permalink
Merge pull request #5 from Picovoice/enable-pcm-to-file
Browse files Browse the repository at this point in the history
  • Loading branch information
ksyeo1010 authored Aug 7, 2024
2 parents ca76602 + 7ed9fb9 commit ba0f5b6
Show file tree
Hide file tree
Showing 22 changed files with 267 additions and 74 deletions.
1 change: 1 addition & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:

# GitHub Actions runners do not have sound cards, so a virtual one must be created in order for unit tests to run.
- name: Install PulseAudio on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y pulseaudio
Expand Down
22 changes: 19 additions & 3 deletions binding/python/_pvspeaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,24 @@ def __init__(
self._version_func.argtypes = None
self._version_func.restype = c_char_p

self._write_to_file_func = library.pv_speaker_write_to_file
self._write_to_file_func.argtypes = [POINTER(self.CPvSpeaker), c_char_p]
self._write_to_file_func.restype = self.PvSpeakerStatuses

def delete(self) -> None:
"""Releases any resources used by PvSpeaker."""

self._delete_func(self._handle)

def start(self) -> None:
"""Starts buffering audio frames."""
"""Starts the audio output device."""

status = self._start_func(self._handle)
if status is not self.PvSpeakerStatuses.SUCCESS:
raise self._PVSPEAKER_STATUS_TO_EXCEPTION[status]("Failed to start device.")

def stop(self) -> None:
"""Stops the device."""
"""Stops the audio output device."""

status = self._stop_func(self._handle)
if status is not self.PvSpeakerStatuses.SUCCESS:
Expand All @@ -186,6 +190,8 @@ def write(self, pcm) -> int:
Synchronous call to write PCM data to the internal circular buffer for audio playback.
Only writes as much PCM data as the internal circular buffer can currently fit, and
returns the length of the PCM data that was successfully written.
:return: Length of the PCM data that was successfully written.
"""

written_length = c_int32()
Expand All @@ -200,6 +206,8 @@ def flush(self, pcm=None) -> int:
"""
Synchronous call to write PCM data to the internal circular buffer for audio playback.
This call blocks the thread until all PCM data has been successfully written and played.
:return: Length of the PCM data that was successfully written.
"""

if pcm is None:
Expand All @@ -208,10 +216,18 @@ def flush(self, pcm=None) -> int:
status = self._flush_func(
self._handle, c_char_p(self._pcm_to_bytes(pcm)), c_int32(len(pcm)), byref(written_length))
if status is not self.PvSpeakerStatuses.SUCCESS:
raise self._PVSPEAKER_STATUS_TO_EXCEPTION[status]("Failed to write to device.")
raise self._PVSPEAKER_STATUS_TO_EXCEPTION[status]("Failed to flush PCM data.")

return written_length.value

def write_to_file(self, output_path: str) -> None:
"""Writes PCM data passed to PvSpeaker to a specified WAV file."""

status = self._write_to_file_func(self._handle, output_path.encode("utf-8"))
if status is not self.PvSpeakerStatuses.SUCCESS:
raise self._PVSPEAKER_STATUS_TO_EXCEPTION[status](
"Failed to open FILE object. PCM data will not be written.")

@property
def is_started(self) -> bool:
"""Gets whether the speaker has started and is available to receive pcm frames or not."""
Expand Down
2 changes: 1 addition & 1 deletion binding/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@

setuptools.setup(
name="pvspeaker",
version="1.0.1",
version="1.0.2",
author="Picovoice",
author_email="[email protected]",
description="Speaker library for Picovoice.",
Expand Down
9 changes: 7 additions & 2 deletions binding/python/test_pv_speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# specific language governing permissions and limitations under the License.
#

import os.path
import os
import unittest

from _pvspeaker import *
Expand Down Expand Up @@ -41,7 +41,7 @@ def test_start_stop(self):
try:
speaker = PvSpeaker(16000, 16, 20)
speaker.start()
pcm = [0] * (512 * 2)
pcm = [0] * 1000
speaker.write(pcm)
speaker.flush(pcm)
speaker.flush()
Expand All @@ -59,6 +59,9 @@ def test_write_flow(self):

speaker = PvSpeaker(sample_rate, 16, buffer_size_secs)
speaker.start()
output_path = "tmp.wav"
speaker.write_to_file(output_path)
self.assertTrue(os.path.exists(output_path))

write_count = speaker.write(pcm)
self.assertEqual(write_count, circular_buffer_size)
Expand All @@ -67,7 +70,9 @@ def test_write_flow(self):
write_count = speaker.flush()
self.assertEqual(write_count, 0)

speaker.stop()
speaker.delete()
os.remove(output_path)

def test_is_started(self):
speaker = PvSpeaker(16000, 16, 20)
Expand Down
49 changes: 31 additions & 18 deletions demo/c/pv_speaker_demo.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ pv_speaker_t *speaker = NULL;
void interrupt_handler(int _) {
(void) _;
is_interrupted = true;
pv_speaker_stop(speaker);
pv_speaker_status_t status = pv_speaker_stop(speaker);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to stop device with %s.\n", pv_speaker_status_to_string(status));
exit(1);
}
fprintf(stdout, "\nStopped...\n");
}

static struct option long_options[] = {
{"show_audio_devices", no_argument, NULL, 's'},
{"input_wav_path", required_argument, NULL, 'i'},
{"audio_device_index", required_argument, NULL, 'd'},
{"buffer_size_secs", required_argument, NULL, 'b'}
{"buffer_size_secs", required_argument, NULL, 'b'},
{"output_wav_path", required_argument, NULL, 'o'}
};

static void print_usage(const char *program_name) {
Expand Down Expand Up @@ -135,9 +140,10 @@ int main(int argc, char *argv[]) {
const char *input_wav_path = NULL;
int32_t device_index = -1;
int32_t buffer_size_secs = 20;
const char *output_wav_path = NULL;

int c;
while ((c = getopt_long(argc, argv, "si:d:b:", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "si:d:b:o:", long_options, NULL)) != -1) {
switch (c) {
case 's':
show_audio_devices();
Expand All @@ -151,6 +157,9 @@ int main(int argc, char *argv[]) {
case 'b':
buffer_size_secs = (int32_t) strtol(optarg, NULL, 10);
break;
case 'o':
output_wav_path = optarg;
break;
default:
exit(1);
}
Expand Down Expand Up @@ -183,6 +192,10 @@ int main(int argc, char *argv[]) {
const char *selected_device = pv_speaker_get_selected_device(speaker);
fprintf(stdout, "Selected device: %s.\n", selected_device);

if (output_wav_path != NULL) {
pv_speaker_write_to_file(speaker, output_wav_path);
}

status = pv_speaker_start(speaker);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to start device with %s.\n", pv_speaker_status_to_string(status));
Expand Down Expand Up @@ -211,25 +224,25 @@ int main(int argc, char *argv[]) {
free(pcm);
}

fprintf(stdout, "Waiting for audio to finish...\n");
int32_t pcm_length = 0;
int16_t pcm[pcm_length];
int8_t *pcm_ptr = (int8_t *) pcm;
int32_t written_length = 0;
status = pv_speaker_flush(speaker, pcm_ptr, pcm_length, &written_length);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to flush pcm with %s.\n", pv_speaker_status_to_string(status));
exit(1);
if (!is_interrupted) {
fprintf(stdout, "Waiting for audio to finish...\n");
int8_t *pcm = NULL;
int32_t pcm_length = 0;
int32_t written_length = 0;
status = pv_speaker_flush(speaker, pcm, pcm_length, &written_length);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to flush pcm with %s.\n", pv_speaker_status_to_string(status));
exit(1);
}
}

if (!is_interrupted) {
fprintf(stdout, "Finished playing audio...\n");
}

status = pv_speaker_stop(speaker);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to stop device with %s.\n", pv_speaker_status_to_string(status));
exit(1);
status = pv_speaker_stop(speaker);
if (status != PV_SPEAKER_STATUS_SUCCESS) {
fprintf(stderr, "Failed to stop device with %s.\n", pv_speaker_status_to_string(status));
exit(1);
}
}

fprintf(stdout, "Deleting pv_speaker...\n");
Expand Down
14 changes: 12 additions & 2 deletions demo/python/pv_speaker_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def main():
type=int,
default=20)

parser.add_argument(
"--output_wav_path",
"-o",
help="Path to the output WAV file where the PCM data passed to PvSpeaker will be written.",
default=None)

args = parser.parse_args()

if args.show_audio_devices:
Expand All @@ -71,6 +77,7 @@ def main():
device_index = args.audio_device_index
input_path = args.input_wav_path
buffer_size_secs = args.buffer_size_secs
output_path = args.output_wav_path

wavfile = None
speaker = None
Expand Down Expand Up @@ -120,6 +127,9 @@ def main():
pcm_list = split_list(pcm, sample_rate)
speaker.start()

if output_path:
speaker.write_to_file(output_path)

print("Playing audio...")
for pcm_sublist in pcm_list:
sublist_length = len(pcm_sublist)
Expand All @@ -142,11 +152,11 @@ def main():
wavfile.close()

except KeyboardInterrupt:
speaker.stop()
print("\nStopped...")
speaker.stop()
finally:
print("Deleting PvSpeaker...")
if speaker is not None:
print("Deleting PvSpeaker...")
speaker.delete()
if wavfile is not None:
wavfile.close()
Expand Down
2 changes: 1 addition & 1 deletion demo/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pvspeaker==1.0.1
pvspeaker==1.0.2
4 changes: 2 additions & 2 deletions demo/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@

setuptools.setup(
name="pvspeakerdemo",
version="1.0.1",
version="1.0.2",
author="Picovoice",
author_email="[email protected]",
description="Speaker library for Picovoice.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/Picovoice/pvspeaker",
packages=["pvspeakerdemo"],
install_requires=["pvspeaker==1.0.1"],
install_requires=["pvspeaker==1.0.2"],
include_package_data=True,
classifiers=[
"Development Status :: 5 - Production/Stable",
Expand Down
Binary file modified lib/linux/x86_64/libpv_speaker.so
Binary file not shown.
Binary file modified lib/mac/arm64/libpv_speaker.dylib
Binary file not shown.
Binary file modified lib/mac/x86_64/libpv_speaker.dylib
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a53-aarch64/libpv_speaker.so
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a53/libpv_speaker.so
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a72-aarch64/libpv_speaker.so
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a72/libpv_speaker.so
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a76-aarch64/libpv_speaker.so
Binary file not shown.
Binary file modified lib/raspberry-pi/cortex-a76/libpv_speaker.so
Binary file not shown.
Binary file modified lib/windows/amd64/libpv_speaker.dll
Binary file not shown.
12 changes: 11 additions & 1 deletion project/include/pv_speaker.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ PV_API pv_speaker_status_t pv_speaker_write(pv_speaker_t *object, int8_t *pcm, i
PV_API pv_speaker_status_t pv_speaker_flush(pv_speaker_t *object, int8_t *pcm, int32_t pcm_length, int32_t *written_length);

/**
* Stops the device.
* Stops the audio output device.
*
* @param object PvSpeaker object.
* @return Status Code. Returns PV_SPEAKER_STATUS_INVALID_ARGUMENT or PV_SPEAKER_STATUS_INVALID_STATE on failure.
Expand Down Expand Up @@ -172,4 +172,14 @@ PV_API const char *pv_speaker_status_to_string(pv_speaker_status_t status);
*/
PV_API const char *pv_speaker_version(void);

/**
* Writes PCM data passed to PvSpeaker to a specified WAV file.
*
* @param object PvSpeaker object.
* @param output_wav_path Path to the output WAV file where the PCM data will be written.
* @return Status Code. Returns PV_SPEAKER_STATUS_RUNTIME_ERROR or PV_SPEAKER_STATUS_INVALID_ARGUMENT on failure.
*/

PV_API pv_speaker_status_t pv_speaker_write_to_file(pv_speaker_t *object, const char *output_wav_path);

#endif //PV_SPEAKER_H
Loading

0 comments on commit ba0f5b6

Please sign in to comment.