def test_stream_index(self): output = av.open(self.sandboxed('output.mov'), 'w') vstream = output.add_stream('mpeg4', 24) vstream.pix_fmt = 'yuv420p' vstream.width = 320 vstream.height = 240 astream = output.add_stream('mp2', 48000) astream.channels = 2 astream.format = 's16' self.assertEqual(vstream.index, 0) self.assertEqual(astream.index, 1) vframe = VideoFrame(320, 240, 'yuv420p') vpacket = vstream.encode(vframe)[0] self.assertIs(vpacket.stream, vstream) self.assertEqual(vpacket.stream_index, 0) for i in range(10): aframe = AudioFrame('s16', 'stereo', samples=astream.frame_size) aframe.rate = 48000 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] break self.assertIs(apacket.stream, astream) self.assertEqual(apacket.stream_index, 1)
def test_stream_index(self): output = av.open(self.sandboxed('output.mov'), 'w') vstream = output.add_stream('mpeg4', 24) vstream.pix_fmt = 'yuv420p' vstream.width = 320 vstream.height = 240 astream = output.add_stream('mp2', 48000) astream.channels = 2 astream.format = 's16' self.assertEqual(vstream.index, 0) self.assertEqual(astream.index, 1) vframe = VideoFrame(320, 240, 'yuv420p') vpacket = vstream.encode(vframe)[0] self.assertIs(vpacket.stream, vstream) self.assertEqual(vpacket.stream_index, 0) for i in range(10): aframe = AudioFrame('s16', 'stereo', samples=astream.frame_size) aframe.rate = 48000 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] break self.assertIs(apacket.stream, astream) self.assertEqual(apacket.stream_index, 1)
def decode(self, encoded_frame: JitterFrame) -> List[Frame]: frame = AudioFrame(format="s16", layout="mono", samples=SAMPLES_PER_FRAME) frame.planes[0].update(self._convert(encoded_frame.data, SAMPLE_WIDTH)) frame.pts = encoded_frame.timestamp frame.sample_rate = SAMPLE_RATE frame.time_base = TIME_BASE return [frame]
def test_stream_index(self): with av.open(self.sandboxed("output.mov"), "w") as output: vstream = output.add_stream("mpeg4", 24) vstream.pix_fmt = "yuv420p" vstream.width = 320 vstream.height = 240 astream = output.add_stream("mp2", 48000) astream.channels = 2 astream.format = "s16" self.assertEqual(vstream.index, 0) self.assertEqual(astream.index, 1) vframe = VideoFrame(320, 240, "yuv420p") vpacket = vstream.encode(vframe)[0] self.assertIs(vpacket.stream, vstream) self.assertEqual(vpacket.stream_index, 0) for i in range(10): if astream.frame_size != 0: frame_size = astream.frame_size else: # decoder didn't indicate constant frame size frame_size = 1000 aframe = AudioFrame("s16", "stereo", samples=frame_size) aframe.rate = 48000 apackets = astream.encode(aframe) if apackets: apacket = apackets[0] break self.assertIs(apacket.stream, astream) self.assertEqual(apacket.stream_index, 1)
async def recv(self) -> Frame: """ Receive the next :class:`~av.audio.frame.AudioFrame`. The base implementation just reads silence, subclass :class:`AudioStreamTrack` to provide a useful implementation. """ if self.readyState != "live": raise MediaStreamError sample_rate = 8000 samples = int(AUDIO_PTIME * sample_rate) if hasattr(self, "_timestamp"): self._timestamp += samples wait = self._start + (self._timestamp / sample_rate) - time.time() await asyncio.sleep(wait) else: self._start = time.time() self._timestamp = 0 frame = AudioFrame(format="s16", layout="mono", samples=samples) for p in frame.planes: p.update(bytes(p.buffer_size)) frame.pts = self._timestamp frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) return frame
def generate_audio_frame(frame_num, input_format="s16", layout="stereo", sample_rate=44100, frame_size=1024): """ Generate audio frame representing part of the sinusoidal wave :param input_format: default: s16 :param layout: default: stereo :param sample_rate: default: 44100 :param frame_size: default: 1024 :param frame_num: frame number :return: audio frame for sinusoidal wave audio signal slice """ frame = AudioFrame(format=input_format, layout=layout, samples=frame_size) frame.sample_rate = sample_rate frame.pts = frame_num * frame_size for i in range(len(frame.layout.channels)): data = np.zeros(frame_size, dtype=format_dtypes[input_format]) for j in range(frame_size): data[j] = np.sin(2 * np.pi * (frame_num + j) * (i + 1) / float(frame_size)) frame.planes[i].update(data) return frame
def test_pts_missing_time_base(self): resampler = AudioResampler("s16", "mono", 44100) # resample one frame iframe = AudioFrame("s16", "stereo", 1024) iframe.sample_rate = 48000 iframe.pts = 0 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 1) oframe = oframes[0] self.assertEqual(oframe.pts, 925) self.assertEqual(oframe.time_base, Fraction(1, 44100)) self.assertEqual(oframe.sample_rate, 44100) self.assertEqual(oframe.samples, 16)
def test_ndarray_s16p_align_8(self): frame = AudioFrame(format='s16p', layout='stereo', samples=159, align=8) array = frame.to_ndarray() self.assertEqual(array.dtype, '<i2') self.assertEqual(array.shape, (2, 159))
def test_ndarray_s16p_align_8(self): frame = AudioFrame(format="s16p", layout="stereo", samples=159, align=8) array = frame.to_ndarray() self.assertEqual(array.dtype, "i2") self.assertEqual(array.shape, (2, 159))
def create_audio_frame(self, samples, pts, layout="mono", sample_rate=48000): frame = AudioFrame(format="s16", layout=layout, samples=samples) for p in frame.planes: p.update(bytes(p.buffer_size)) frame.pts = pts frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) return frame
def generate_audio_frame(): """Generate a blank audio frame.""" from av import AudioFrame audio_frame = AudioFrame(format='dbl', layout='mono', samples=1024) # audio_bytes = b''.join(b'\x00\x00\x00\x00\x00\x00\x00\x00' # for i in range(0, 1024)) audio_bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00' * 1024 audio_frame.planes[0].update(audio_bytes) audio_frame.sample_rate = AUDIO_SAMPLE_RATE audio_frame.time_base = Fraction(1, AUDIO_SAMPLE_RATE) return audio_frame
def generate_audio_frame(): """Generate a blank audio frame.""" from av import AudioFrame audio_frame = AudioFrame(format='dbl', layout='mono', samples=1024) # audio_bytes = b''.join(b'\x00\x00\x00\x00\x00\x00\x00\x00' # for i in range(0, 1024)) audio_bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00' * 1024 audio_frame.planes[0].update(audio_bytes) audio_frame.sample_rate = AUDIO_SAMPLE_RATE audio_frame.time_base = Fraction(1, AUDIO_SAMPLE_RATE) return audio_frame
def test_basic_to_nd_array(self): frame = AudioFrame(format='s16p', layout='stereo', samples=160) with warnings.catch_warnings(record=True) as recorded: array = frame.to_nd_array() self.assertEqual(array.shape, (2, 160)) # check deprecation warning self.assertEqual(len(recorded), 1) self.assertEqual(recorded[0].category, AttributeRenamedWarning) self.assertEqual( str(recorded[0].message), 'AudioFrame.to_nd_array is deprecated; please use AudioFrame.to_ndarray.')
def test_pts_missing_time_base(self): resampler = AudioResampler('s16', 'mono', 44100) iframe = AudioFrame('s16', 'stereo', 1024) iframe.sample_rate = 48000 iframe.pts = 0 oframe = resampler.resample(iframe) self.assertIs(oframe.pts, None) self.assertIs(oframe.time_base, None) self.assertEqual(oframe.sample_rate, 44100)
def create_audio_frame(sample_func, samples, pts, layout="mono", sample_rate=48000): frame = AudioFrame(format="s16", layout=layout, samples=samples) for p in frame.planes: buf = bytearray() for i in range(samples): sample = int(sample_func(i) * 32767) buf.extend(int.to_bytes(sample, 2, sys.byteorder, signed=True)) p.update(buf) frame.pts = pts frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) return frame
def test_pts_missing_time_base(self): resampler = AudioResampler('s16', 'mono', 44100) iframe = AudioFrame('s16', 'stereo', 1024) iframe.sample_rate = 48000 iframe.pts = 0 oframe = resampler.resample(iframe) self.assertIs(oframe.pts, None) self.assertIs(oframe.time_base, None) self.assertEqual(oframe.sample_rate, 44100)
def test_basic_to_nd_array(self): frame = AudioFrame(format='s16p', layout='stereo', samples=160) with warnings.catch_warnings(record=True) as recorded: array = frame.to_nd_array() self.assertEqual(array.shape, (2, 160)) # check deprecation warning self.assertEqual(len(recorded), 1) self.assertEqual(recorded[0].category, AttributeRenamedWarning) self.assertEqual( str(recorded[0].message), 'AudioFrame.to_nd_array is deprecated; please use AudioFrame.to_ndarray.' )
def decode(self, encoded_frame): frame = AudioFrame(format='s16', layout='stereo', samples=SAMPLES_PER_FRAME) frame.pts = encoded_frame.timestamp frame.sample_rate = SAMPLE_RATE frame.time_base = TIME_BASE length = lib.opus_decode( self.decoder, encoded_frame.data, len(encoded_frame.data), ffi.cast('int16_t *', frame.planes[0].buffer_ptr), SAMPLES_PER_FRAME, 0) assert length == SAMPLES_PER_FRAME return [frame]
def create_audio_frames(self, layout, sample_rate, count): frames = [] timestamp = 0 samples_per_frame = int(AUDIO_PTIME * sample_rate) for i in range(count): frame = AudioFrame(format="s16", layout=layout, samples=samples_per_frame) for p in frame.planes: p.update(bytes(p.buffer_size)) frame.pts = timestamp frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) frames.append(frame) timestamp += samples_per_frame return frames
def test_manual_s16_mono_constructor_align_8(self): frame = AudioFrame(format="s16", layout="mono", samples=159, align=8) self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "mono") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 320) self.assertEqual(frame.samples, 159)
def test_manual_s16_stereo_constructor(self): frame = AudioFrame(format="s16", layout="stereo", samples=160) self.assertEqual(frame.format.name, "s16") self.assertEqual(frame.layout.name, "stereo") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 640) self.assertEqual(frame.samples, 160)
def test_manual_flt_mono_constructor(self): frame = AudioFrame(format="flt", layout="mono", samples=160) self.assertEqual(frame.format.name, "flt") self.assertEqual(frame.layout.name, "mono") self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 640) self.assertEqual(frame.samples, 160)
def test_manual_flt_stereo_constructor(self): frame = AudioFrame(format='flt', layout='stereo', samples=160) self.assertEqual(frame.format.name, 'flt') self.assertEqual(frame.layout.name, 'stereo') self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 1280) self.assertEqual(frame.samples, 160)
async def recv(self): if self.readyState != "live": raise MediaStreamError data = await self.__queue.get() if not data: self.stop() raise MediaStreamError try: indata, _ = data frame = AudioFrame.from_ndarray(indata.reshape(indata.shape[::-1]), format='s16', layout='mono') sample_rate = indata.shape[0] if hasattr(self, "_timestamp"): samples = int((time.time() - self._start) * sample_rate) self._timestamp += samples else: self._start = time.time() self._timestamp = 0 frame.pts = self._timestamp frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) return frame except: Logger.exception('Audio:') self.stop() raise MediaStreamError
def test_manual_s16_mono_constructor(self): frame = AudioFrame(format='s16', layout='mono', samples=160) self.assertEqual(frame.format.name, 's16') self.assertEqual(frame.layout.name, 'mono') self.assertEqual(len(frame.planes), 1) self.assertEqual(frame.planes[0].buffer_size, 320) self.assertEqual(frame.samples, 160)
def decode(self, encoded_frame: JitterFrame) -> List[Frame]: frame = AudioFrame(format="s16", layout="stereo", samples=SAMPLES_PER_FRAME) frame.pts = encoded_frame.timestamp frame.sample_rate = SAMPLE_RATE frame.time_base = TIME_BASE length = lib.opus_decode( self.decoder, encoded_frame.data, len(encoded_frame.data), ffi.cast("int16_t *", frame.planes[0].buffer_ptr), SAMPLES_PER_FRAME, 0, ) assert length == SAMPLES_PER_FRAME return [frame]
async def recv(self) -> Frame: """ Receive the next :class:`~av.audio.frame.AudioFrame`. The base implementation just reads silence, subclass :class:`AudioStreamTrack` to provide a useful implementation. """ if self.readyState != "live": raise Exception("media stream error") # MediaStreamError sample_rate = 16000 # 8000 samples = int(AUDIO_PTIME * sample_rate) # logger.info("WTF!!") if hasattr(self, "_timestamp"): self._timestamp += samples wait = self._start + (self._timestamp / sample_rate) - time.time() # logger.info("wating... {0}".format(wait)) await asyncio.sleep(wait) else: self._start = time.time() self._timestamp = 0 # await asyncio.sleep(0.01) try: # data = sounddevice.rec(frames=samples, samplerate=sample_rate, channels=1, dtype='int16', blocking=True) data, overflowed = self.stream.read(frames=samples) #print("data", data) #print("data", data.shape) #data = np.zeros((2,1152), dtype='int16') #logger.info(data) # frameb = AudioFrame(format="s16", layout="mono", samples=samples) # logger.info('*****************************') # alt_data = frameb.to_ndarray() # logger.info(alt_data) # logger.info(alt_data.shape) # logger.info('-----------------') fixed_data = np.swapaxes(data, 0, 1) #print(fixed_data.shape) #print(fixed_data.dtype) #print(data.dtype) #print(fixed_data.dtype) frame = AudioFrame.from_ndarray( fixed_data, layout="mono", format='s16') # layout="stereo", format='s32') # s16 #for p in frame.planes: # p.update(data.tobytes()) #bytes(p.buffer_size)) # logger.info(frame.planes) # logger.info("\n!!!!!!!!!!!") frame.pts = self._timestamp frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) except Exception as exc: print(exc) logger.exception(exc) return frame
def test_identity_passthrough(self): # If we don't ask it to do anything, it won't. resampler = AudioResampler() iframe = AudioFrame('s16', 'stereo', 1024) oframe = resampler.resample(iframe) self.assertIs(iframe, oframe)
def test_matching_passthrough(self): # If the frames match, it won't do anything. resampler = AudioResampler('s16', 'stereo') iframe = AudioFrame('s16', 'stereo', 1024) oframe = resampler.resample(iframe) self.assertIs(iframe, oframe)
def write_raw(self, data: av.AudioFrame): if self.container is None: self.container = av.open(self.path, 'w') self.stream = self.container.add_stream('aac', rate=self.frame_rate) logger.debug(f"Opened stream: {self.path}") data.pts = None for packet in self.stream.encode(data): self.container.mux(packet)
def test_pts_assertion_new_rate(self): resampler = AudioResampler('s16', 'mono', 44100) iframe = AudioFrame('s16', 'stereo', 1024) iframe.sample_rate = 48000 iframe.time_base = '1/48000' iframe.pts = 0 oframe = resampler.resample(iframe) self.assertEqual(oframe.pts, 0) self.assertEqual(str(oframe.time_base), '1/44100') self.assertEqual(oframe.sample_rate, 44100) samples_out = resampler.samples_out self.assertTrue(samples_out > 0) iframe.pts = 1024 oframe = resampler.resample(iframe) self.assertEqual(oframe.pts, samples_out) self.assertEqual(str(oframe.time_base), '1/44100') self.assertEqual(oframe.sample_rate, 44100)
def test_ndarray_u8(self): layouts = [ ('u8', 'mono', 'u1', (1, 160)), ('u8', 'stereo', 'u1', (1, 320)), ('u8p', 'mono', 'u1', (1, 160)), ('u8p', 'stereo', 'u1', (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.random.randint(0, 256, size=size, dtype=dtype) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertTrue((frame.to_ndarray() == array).all())
def test_matching_passthrough(self): """ If the frames match, it won't do anything. """ resampler = AudioResampler("s16", "stereo") # resample one frame iframe = AudioFrame("s16", "stereo", 1024) oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0)
def test_identity_passthrough(self): """ If we don't ask it to do anything, it won't. """ resampler = AudioResampler() # resample one frame iframe = AudioFrame("s16", "stereo", 1024) oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # resample another frame iframe.pts = 1024 oframes = resampler.resample(iframe) self.assertEqual(len(oframes), 1) self.assertIs(iframe, oframes[0]) # flush oframes = resampler.resample(None) self.assertEqual(len(oframes), 0)
async def recv(self): """ Receive the next :class:`~av.audio.frame.AudioFrame`. The base implementation just reads silence, subclass :class:`AudioStreamTrack` to provide a useful implementation. """ if self.readyState != 'live': raise MediaStreamError sample_rate = 8000 samples = int(AUDIO_PTIME * sample_rate) timestamp = getattr(self, '_timestamp', 0) self._timestamp = timestamp + samples await asyncio.sleep(AUDIO_PTIME) frame = AudioFrame(format='s16', layout='mono', samples=samples) for p in frame.planes: p.update(bytes(p.buffer_size)) frame.pts = timestamp frame.sample_rate = sample_rate frame.time_base = fractions.Fraction(1, sample_rate) return frame
def test_ndarray_dbl(self): layouts = [ ('dbl', 'mono', '<f8', (1, 160)), ('dbl', 'stereo', '<f8', (1, 320)), ('dblp', 'mono', '<f8', (1, 160)), ('dblp', 'stereo', '<f8', (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.ndarray(shape=size, dtype=dtype) for i in range(size[0]): array[i][:] = numpy.random.rand(size[1]) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertTrue((frame.to_ndarray() == array).all())
def test_ndarray_u8(self): layouts = [ ("u8", "mono", "u1", (1, 160)), ("u8", "stereo", "u1", (1, 320)), ("u8p", "mono", "u1", (1, 160)), ("u8p", "stereo", "u1", (2, 160)), ] for format, layout, dtype, size in layouts: array = numpy.random.randint(0, 256, size=size, dtype=dtype) frame = AudioFrame.from_ndarray(array, format=format, layout=layout) self.assertEqual(frame.format.name, format) self.assertEqual(frame.layout.name, layout) self.assertEqual(frame.samples, 160) self.assertNdarraysEqual(frame.to_ndarray(), array)
def test_pts_assertion_same_rate(self): resampler = AudioResampler('s16', 'mono') iframe = AudioFrame('s16', 'stereo', 1024) iframe.sample_rate = 48000 iframe.time_base = '1/48000' iframe.pts = 0 oframe = resampler.resample(iframe) self.assertEqual(oframe.pts, 0) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) iframe.pts = 1024 oframe = resampler.resample(iframe) self.assertEqual(oframe.pts, 1024) self.assertEqual(oframe.time_base, iframe.time_base) self.assertEqual(oframe.sample_rate, iframe.sample_rate) iframe.pts = 9999 self.assertRaises(ValueError, resampler.resample, iframe)
async def recv(self): if self._startTime is None and self._startedCallback is not None: self._startedCallback() try: data = await self._audioSubscription.get() except SubscriptionClosed: self._log.debug( "Audio track finished. raising MediaStreamError to shut down connection" ) self.stop() raise MediaStreamError except: self._log.exception("Got unknown error. Crashing video stream") self.stop() raise MediaStreamError # self._log.exception("FAIL") # self._log.info("GOT AUDIO %d,%d", data.shape[0], data.shape[1]) # self._log.info("Creating FRAME") # https://trac.ffmpeg.org/wiki/audio%20types # https://github.com/mikeboers/PyAV/blob/develop/av/audio/frame.pyx # https://ffmpeg.org/doxygen/3.0/group__channel__mask__c.html # # It looks like at the time of writing, audio samples are only accepted as s16, # and not as float (flt) in aiortc. We therefore use s16 as format instead of flt, # and convert the data: # https://github.com/jlaine/aiortc/blob/master/aiortc/codecs/opus.py # We therefore force a conversion to 16 bit integer: data = (np.clip(data, -1, 1) * 32768).astype(np.int16) new_frame = AudioFrame.from_ndarray( data, format="s16", layout=str(data.shape[0]) + "c" ) # Use the sample rate for the base clock new_frame.sample_rate = self._sampleRate new_frame.time_base = fractions.Fraction(1, self._sampleRate) if self._startTime is None: self._startTime = time.time() # We need to compute the timestamp of the frame. # We want to handle audio without skips, where we simply increase the clock according # to the samples. However, sometimes the data might come in a bit late, which would # mean that we still want to get it correctly. # However, we have no guarantee that the data is actually coming without skipped # points. We try to detect if the data coming seems to be way behind the current # perfect timestamp, and in that situation, we can decide to skip forward if canSkip is True # https://en.wikipedia.org/wiki/Presentation_timestamp # Since our clock rate is simply our sample rate, the timestamp is the number of samples # we should have seen so far new_frame.pts = self._sampleNumber self._sampleNumber += data.shape[1] perfectSampleNumber = ( int((time.time() - self._startTime) * self._sampleRate) + data.shape[1] ) # print(perfectSampleNumber - self._sampleNumber) if self._canSkip: if perfectSampleNumber - self._sampleRate * 1 > self._sampleNumber: # The audio is over 1 second behind where it is supposed to be. # Adjust the sample number to the ``corrected" version self._log.warn( "Received audio is over 1 second behind optimal timestamp! Skipping audio forward! Use canSkip=False to disable this correction" ) new_frame.pts = perfectSampleNumber - data.shape[1] if perfectSampleNumber + self._sampleRate * 2 < self._sampleNumber: # If the audio stream is over 2 seconds ahead, we wait 1 second before continuing self._log.debug("Stream is over 2 seconds ahead. Sleeping for 1 second.") await asyncio.sleep(1) # print("\n\nSENDING DATA", new_frame, new_frame.time_base) self._log.debug("Writing frame %s", new_frame) return new_frame
def test_basic_to_ndarray(self): frame = AudioFrame(format='s16p', layout='stereo', samples=160) array = frame.to_ndarray() self.assertEqual(array.dtype, '<i2') self.assertEqual(array.shape, (2, 160))
def test_ndarray_s16p_align_8(self): frame = AudioFrame(format='s16p', layout='stereo', samples=159, align=8) array = frame.to_ndarray() self.assertEqual(array.dtype, '<i2') self.assertEqual(array.shape, (2, 159))