Comment lire l'audio en streaming avec pyglet?
le but de cette question Est d'essayer de comprendre comment jouer l'audio en streaming en utilisant pyglet. La première est de s'assurer que vous êtes en mesure de lire des fichiers mp3 en utilisant pyglet, c'est le but de ce premier extrait:
import sys
import inspect
import requests
import pyglet
from pyglet.media import *
pyglet.lib.load_library('avbin')
pyglet.have_avbin = True
def url_to_filename(url):
return url.split('/')[-1]
def download_file(url, filename=None):
filename = filename or url_to_filename(url)
with open(filename, "wb") as f:
print("Downloading %s" % filename)
response = requests.get(url, stream=True)
total_length = response.headers.get('content-length')
if total_length is None:
f.write(response.content)
else:
dl = 0
total_length = int(total_length)
for data in response.iter_content(chunk_size=4096):
dl += len(data)
f.write(data)
done = int(50 * dl / total_length)
sys.stdout.write("r[%s%s]" % ('=' * done, ' ' * (50 - done)))
sys.stdout.flush()
url = "https://freemusicarchive.org/file/music/ccCommunity/DASK/Abiogenesis/DASK_-_08_-_Protocell.mp3"
filename = "mcve.mp3"
download_file(url, filename)
music = pyglet.media.load(filename)
music.play()
pyglet.app.run()
Si vous avez installé les bibliothèques pip install pyglet requests
et aussi installé AVBin à ce stade, vous devriez être en mesure d'écouter les fichiers mp3 une fois qu'il est téléchargé.
une fois que nous aurons atteint ce point, j'aimerais savoir comment jouer & buffering le fichier d'une manière similaire à la plupart des lecteurs web vidéo/audio existants en utilisant les requêtes pyglet+. Cela signifie jouer les fichiers sans attendre jusqu'à ce que le fichier ait été téléchargé complètement.
Après la lecture de la pyglet médias docs vous pouvez voir qu'il y a à disposition de ces classes:
media
sources
base
AudioData
AudioFormat
Source
SourceGroup
SourceInfo
StaticSource
StreamingSource
VideoFormat
player
Player
PlayerGroup
j'ai vu qu'il y a d'autres questions similaires, mais elles n'ont pas été résolues correctement et leur contenu ne fournit pas beaucoup de renseignements pertinents. détails:
- Lecture audio en continu à l'aide de pyglet
- Comment puis-je lire le flux audio sans le sauvegarder dans le fichier avec pyglet?
C'est pourquoi j'ai créé une nouvelle question. Comment lire l'audio en streaming avec pyglet? Pourriez-vous fournir un petit exemple en utilisant la mcve ci-dessus comme base?
1 réponses
en Supposant que vous ne souhaitez pas importer un nouveau package pour le faire pour vous - cela peut être fait avec un peu d'effort.
tout d'abord, dirigeons-nous vers le code source de Pyglet et regardons media.load
media/__init__.py
.
"""Load a Source from a file.
All decoders that are registered for the filename extension are tried.
If none succeed, the exception from the first decoder is raised.
You can also specifically pass a decoder to use.
:Parameters:
`filename` : str
Used to guess the media format, and to load the file if `file` is
unspecified.
`file` : file-like object or None
Source of media data in any supported format.
`streaming` : bool
If `False`, a :class:`StaticSource` will be returned; otherwise
(default) a :class:`~pyglet.media.StreamingSource` is created.
`decoder` : MediaDecoder or None
A specific decoder you wish to use, rather than relying on
automatic detection. If specified, no other decoders are tried.
:rtype: StreamingSource or Source
"""
if decoder:
return decoder.decode(file, filename, streaming)
else:
first_exception = None
for decoder in get_decoders(filename):
try:
loaded_source = decoder.decode(file, filename, streaming)
return loaded_source
except MediaDecodeException as e:
if not first_exception or first_exception.exception_priority < e.exception_priority:
first_exception = e
# TODO: Review this:
# The FFmpeg codec attempts to decode anything, so this codepath won't be reached.
if not first_exception:
raise MediaDecodeException('No decoders are available for this media format.')
raise first_exception
add_default_media_codecs()
La ligne critique est ici loaded_source = decoder.decode(...)
. Essentiellement, pour charger Audio Pyglet prend un dossier et le transporte à un décodeur de médias (par exemple. FFMPEG), qui renvoie alors une liste de 'frames' ou de paquets que Pyglet peut jouer avec unPlayer
classe. Si le format audio est comprimé (p. ex. mp3 ou aac), Pyglet utilisera une bibliothèque externe (actuellement seule AVBin est prise en charge) pour la convertir en brut, audio décompressé. Vous le savez probablement déjà, certains de cela.
donc si nous voulons voir comment nous pouvons bourrer un flux d'octets dans le moteur audio de Pyglet plutôt qu'un fichier, nous aurons besoin de jeter un oeil à l'un des décodeurs. Pour cet exemple, utilisons FFMPEG car C'est le plus facile d'accès.
Dans media/codecs/ffmpeg.py
:
class FFmpegDecoder(object):
def get_file_extensions(self):
return ['.mp3', '.ogg']
def decode(self, file, filename, streaming):
if streaming:
return FFmpegSource(filename, file)
else:
return StaticSource(FFmpegSource(filename, file))
L'objet dont il hérite est MediaDecoder
, trouvé dans media/codecs/__init__.py
. De retour à l' load
function media/__init__.py
, vous verrez que pyglet choisira un MediaDecoder basé sur l'extension de fichier, puis retournera son decode
function avec le fichier comme paramètre pour obtenir l'audio sous la forme d'un flux de paquets. Que de flux de paquets est un Source
objet; chaque décodeur a sa propre saveur, sous la forme de StaticSource ou StreamingSource. Le premier est utilisé pour stocker l'audio en mémoire, et le dernier à jouer immédiatement. Le décodeur de FFmpeg ne supporte que StreamingSource.
nous pouvons voir que FFMPEG's est FFmpegSource, également situé dans media/codecs/ffmpeg.py
. Nous trouvons ce Goliath d'une classe:
class FFmpegSource(StreamingSource):
# Max increase/decrease of original sample size
SAMPLE_CORRECTION_PERCENT_MAX = 10
def __init__(self, filename, file=None):
if file is not None:
raise NotImplementedError('Loading from file stream is not supported')
self._file = ffmpeg_open_filename(asbytes_filename(filename))
if not self._file:
raise FFmpegException('Could not open "{0}"'.format(filename))
self._video_stream = None
self._video_stream_index = None
self._audio_stream = None
self._audio_stream_index = None
self._audio_format = None
self.img_convert_ctx = POINTER(SwsContext)()
self.audio_convert_ctx = POINTER(SwrContext)()
file_info = ffmpeg_file_info(self._file)
self.info = SourceInfo()
self.info.title = file_info.title
self.info.author = file_info.author
self.info.copyright = file_info.copyright
self.info.comment = file_info.comment
self.info.album = file_info.album
self.info.year = file_info.year
self.info.track = file_info.track
self.info.genre = file_info.genre
# Pick the first video and audio streams found, ignore others.
for i in range(file_info.n_streams):
info = ffmpeg_stream_info(self._file, i)
if isinstance(info, StreamVideoInfo) and self._video_stream is None:
stream = ffmpeg_open_stream(self._file, i)
self.video_format = VideoFormat(
width=info.width,
height=info.height)
if info.sample_aspect_num != 0:
self.video_format.sample_aspect = (
float(info.sample_aspect_num) /
info.sample_aspect_den)
self.video_format.frame_rate = (
float(info.frame_rate_num) /
info.frame_rate_den)
self._video_stream = stream
self._video_stream_index = i
elif (isinstance(info, StreamAudioInfo) and
info.sample_bits in (8, 16) and
self._audio_stream is None):
stream = ffmpeg_open_stream(self._file, i)
self.audio_format = AudioFormat(
channels=min(2, info.channels),
sample_size=info.sample_bits,
sample_rate=info.sample_rate)
self._audio_stream = stream
self._audio_stream_index = i
channel_input = avutil.av_get_default_channel_layout(info.channels)
channels_out = min(2, info.channels)
channel_output = avutil.av_get_default_channel_layout(channels_out)
sample_rate = stream.codec_context.contents.sample_rate
sample_format = stream.codec_context.contents.sample_fmt
if sample_format in (AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_U8P):
self.tgt_format = AV_SAMPLE_FMT_U8
elif sample_format in (AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16P):
self.tgt_format = AV_SAMPLE_FMT_S16
elif sample_format in (AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S32P):
self.tgt_format = AV_SAMPLE_FMT_S32
elif sample_format in (AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP):
self.tgt_format = AV_SAMPLE_FMT_S16
else:
raise FFmpegException('Audio format not supported.')
self.audio_convert_ctx = swresample.swr_alloc_set_opts(None,
channel_output,
self.tgt_format, sample_rate,
channel_input, sample_format,
sample_rate,
0, None)
if (not self.audio_convert_ctx or
swresample.swr_init(self.audio_convert_ctx) < 0):
swresample.swr_free(self.audio_convert_ctx)
raise FFmpegException('Cannot create sample rate converter.')
self._packet = ffmpeg_init_packet()
self._events = [] # They don't seem to be used!
self.audioq = deque()
# Make queue big enough to accomodate 1.2 sec?
self._max_len_audioq = 50 # Need to figure out a correct amount
if self.audio_format:
# Buffer 1 sec worth of audio
self._audio_buffer = \
(c_uint8 * ffmpeg_get_audio_buffer_size(self.audio_format))()
self.videoq = deque()
self._max_len_videoq = 50 # Need to figure out a correct amount
self.start_time = self._get_start_time()
self._duration = timestamp_from_ffmpeg(file_info.duration)
self._duration -= self.start_time
# Flag to determine if the _fillq method was already scheduled
self._fillq_scheduled = False
self._fillq()
# Don't understand why, but some files show that seeking without
# reading the first few packets results in a seeking where we lose
# many packets at the beginning.
# We only seek back to 0 for media which have a start_time > 0
if self.start_time > 0:
self.seek(0.0)
---
[A few hundred lines more...]
---
def get_next_video_timestamp(self):
if not self.video_format:
return
if self.videoq:
while True:
# We skip video packets which are not video frames
# This happens in mkv files for the first few frames.
video_packet = self.videoq[0]
if video_packet.image == 0:
self._decode_video_packet(video_packet)
if video_packet.image is not None:
break
self._get_video_packet()
ts = video_packet.timestamp
else:
ts = None
if _debug:
print('Next video timestamp is', ts)
return ts
def get_next_video_frame(self, skip_empty_frame=True):
if not self.video_format:
return
while True:
# We skip video packets which are not video frames
# This happens in mkv files for the first few frames.
video_packet = self._get_video_packet()
if video_packet.image == 0:
self._decode_video_packet(video_packet)
if video_packet.image is not None or not skip_empty_frame:
break
if _debug:
print('Returning', video_packet)
return video_packet.image
def _get_start_time(self):
def streams():
format_context = self._file.context
for idx in (self._video_stream_index, self._audio_stream_index):
if idx is None:
continue
stream = format_context.contents.streams[idx].contents
yield stream
def start_times(streams):
yield 0
for stream in streams:
start = stream.start_time
if start == AV_NOPTS_VALUE:
yield 0
start_time = avutil.av_rescale_q(start,
stream.time_base,
AV_TIME_BASE_Q)
start_time = timestamp_from_ffmpeg(start_time)
yield start_time
return max(start_times(streams()))
@property
def audio_format(self):
return self._audio_format
@audio_format.setter
def audio_format(self, value):
self._audio_format = value
if value is None:
self.audioq.clear()
La ligne que vous serez intéressé par ici self._file = ffmpeg_open_filename(asbytes_filename(filename))
. Cela nous amène ici, une fois de plus en media/codecs/ffmpeg.py
:
def ffmpeg_open_filename(filename):
"""Open the media file.
:rtype: FFmpegFile
:return: The structure containing all the information for the media.
"""
file = FFmpegFile() # TODO: delete this structure and use directly AVFormatContext
result = avformat.avformat_open_input(byref(file.context),
filename,
None,
None)
if result != 0:
raise FFmpegException('Error opening file ' + filename.decode("utf8"))
result = avformat.avformat_find_stream_info(file.context, None)
if result < 0:
raise FFmpegException('Could not find stream info')
return file
et c'est là que les choses deviennent confuses: il appelle à une ctypes fonction (avformat_open_input) que lorsqu'un fichier, va récupérer ses détails et remplissez toutes les informations dont il a besoin pour notre classe FFmpegSource. Avec un peu de travail, vous devriez être en mesure d'obtenir avformat_open_input pour prendre un objet bytes plutôt qu'un chemin vers un fichier qu'il ouvrira pour obtenir les mêmes informations. J'aimerais bien faire et d'inclure un exemple de travail, mais je n'ai pas le temps maintenant. Vous devez alors créer une nouvelle fonction ffmpeg_open_filename en utilisant la nouvelle fonction avformat_open_input, puis une nouvelle classe FFmpegSource en utilisant la nouvelle fonction ffmpeg_open_filename. Tout ce dont vous avez besoin maintenant est une nouvelle classe FFmpegDecoder utilisant la nouvelle classe FFmpegSource.
vous pouvez alors implémenter ceci en l'ajoutant directement à votre paquet pyglet. Après, vous voulez ajouter le support d'un argument byte object dans la fonction load () (situé dans media/__init__.py
et outrepassez le décodeur de votre nouveau. Et là, vous seriez maintenant en mesure de stream audio sans le sauvegarder.
Ou, vous pouvez simplement utiliser un package qui supporte déjà. Python-vlc ne. Vous pouvez utiliser l'exemple ici pour lire l'audio que vous voulez à partir d'un lien. Si vous ne faites pas cela juste pour un défi, je vous recommandons vivement d'utiliser un autre paquet. Sinon: bonne chance.