Skip to content

Commit

Permalink
sounddevice renderer working
Browse files Browse the repository at this point in the history
  • Loading branch information
dschreij committed May 28, 2016
1 parent 96ba10a commit 0898941
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 12 deletions.
2 changes: 1 addition & 1 deletion mediadecoder/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def __audiorender_thread(self):
# if the current one gives a problem
try:
start = self.audio_times.pop(0)
stop = self.audio_times[1]
stop = self.audio_times[0]
except IndexError:
logger.debug("Audio times could not be obtained")
time.sleep(0.02)
Expand Down
19 changes: 16 additions & 3 deletions mediadecoder/soundrenderers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from mediadecoder.soundrenderers.pyaudiorenderer import SoundrendererPyAudio
from mediadecoder.soundrenderers.pygamerenderer import SoundrendererPygame
from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
import warnings

try:
from mediadecoder.soundrenderers.pyaudiorenderer import SoundrendererPyAudio
except Exception as e:
warnings.warn("Could not import pyaudio sound renderer: {}".format(e))

try:
from mediadecoder.soundrenderers.pygamerenderer import SoundrendererPygame
except Exception as e:
warnings.warn("Could not import pygame sound renderer: {}".format(e))

try:
from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
except Exception as e:
warnings.warn("Could not import sounddevice sound renderer: {}".format(e))

__all__ = ['SoundrendererPygame', 'SoundrendererPyAudio','SoundrendererSounddevice']

16 changes: 11 additions & 5 deletions mediadecoder/soundrenderers/pygamerenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,18 @@ def __init__(self, audioformat, queue=None):
fps = audioformat["fps"]
nchannels = audioformat["nchannels"]
nbytes = audioformat["nbytes"]
buffersize = audioformat["buffersize"]

pygame.mixer.quit()
pygame.mixer.init(fps, -8 * nbytes, nchannels)
pygame.mixer.init(fps, -8 * nbytes, nchannels, buffersize)

def run(self):
""" Main thread function. """
if not hasattr(self, 'queue'):
raise RuntimeError("Audio queue is not intialized.")

chunk = None
channel = None
self.keep_listening = True
while self.keep_listening:
if chunk is None:
Expand All @@ -60,12 +62,16 @@ def run(self):
except Empty:
continue

if not hasattr(self,"channel"):
self.channel = chunk.play()
if channel is None:
channel = chunk.play()
else:
if not pygame.mixer.get_busy():
self.channel.queue(chunk)
if not channel.get_queue():
channel.queue(chunk)
chunk = None

if not channel is None:
channel.stop()
pygame.mixer.quit()

def close_stream(self):
""" Cleanup (done by pygame.quit() in main loop) """
Expand Down
10 changes: 8 additions & 2 deletions mediadecoder/soundrenderers/sounddevicerenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def __init__(self, audioformat, queue=None):
self.stream = sd.OutputStream(
channels = audioformat["nchannels"],
samplerate = audioformat["fps"],
blocksize = audioformat["buffersize"]*2,
dtype = 'int{}'.format(audioformat['nbytes']*8),
blocksize = audioformat["buffersize"],
callback = self.get_frame
)
self.keep_listening = True
Expand All @@ -41,7 +42,12 @@ def get_frame(self, outdata, frames, timedata, status):
""" Callback function for the audio stream. Don't use directly. """
try:
chunk = self.queue.get_nowait()
outdata[:] = chunk
# Check if the chunk contains the expected number of frames
# The callback function raises a ValueError otherwise.
if chunk.shape[0] == frames:
outdata[:] = chunk
else:
outdata.fill(0)
except Empty:
outdata.fill(0)

Expand Down
73 changes: 73 additions & 0 deletions mediadecoder/soundrenderers/sounddevicerenderer2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

""" This is an alternative implementation of sounddevicerenderer, that doesn't use
the callback functionality of sounddevice's OutputStream. The threading is done by
python, instead of C (under the hood) by sounddevice. I haven't determined yet
which method is better, so I am leaving them both in for now. """


import threading
import sounddevice as sd
import logging
logger = logging.getLogger(__name__)

try:
# Python 3
from queue import Queue, Empty
except:
# Python 2
from Queue import Queue, Empty

from mediadecoder.soundrenderers._base import SoundRenderer

queue_timeout=0.01

class SoundrendererSounddevice(threading.Thread, SoundRenderer):
""" Uses pyaudio to play sound """
def __init__(self, audioformat, queue=None):
"""Constructor.
Creates a pyaudio sound renderer.
Parameters
----------
audioformat : dict
A dictionary containing the properties of the audiostream
queue : Queue.queue
A queue object which serves as a buffer on which the individual
audio frames are placed by the decoder.
"""
super(SoundrendererSounddevice, self).__init__()

if not queue is None:
self.queue = queue

self.stream = sd.OutputStream(
channels = audioformat["nchannels"],
samplerate = audioformat["fps"],
dtype = 'int{}'.format(audioformat['nbytes']*8),
blocksize = audioformat["buffersize"],
)


def run(self):
""" Initializes the stream. """
if not hasattr(self, 'queue'):
raise RuntimeError("Audio queue is not intialized.")
self.keep_listening = True
self.stream.start()

while self.keep_listening:
try:
chunk = self.queue.get(timeout=queue_timeout)
underflowed = self.stream.write(chunk)
if underflowed:
logger.debug("Buffer underrun")
except Empty:
pass

self.stream.stop()
self.stream.close()


def close_stream(self):
""" Closes the stream. Performs cleanup. """
self.keep_listening = False
4 changes: 3 additions & 1 deletion play.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import mediadecoder # For the state constants
from mediadecoder.decoder import Decoder
from mediadecoder.soundrenderers import *

class VideoPlayer():
""" This is an example videoplayer that uses pygame+pyopengl to render a video.
Expand Down Expand Up @@ -132,10 +131,13 @@ def load_media(self, vidSource):

if(self.decoder.audioformat):
if self.soundrenderer == "pygame":
from mediadecoder.soundrenderers import SoundrendererPygame
self.audio = SoundrendererPygame(self.decoder.audioformat)
elif self.soundrenderer == "pyaudio":
from mediadecoder.soundrenderers import SoundrendererPyAudio
self.audio = SoundrendererPyAudio(self.decoder.audioformat)
elif self.soundrenderer == "sounddevice":
from mediadecoder.soundrenderers import SoundrendererSounddevice
self.audio = SoundrendererSounddevice(self.decoder.audioformat)
self.decoder.set_audiorenderer(self.audio)

Expand Down

0 comments on commit 0898941

Please sign in to comment.