Ejemplo n.º 1
0
def createDecoder(freq, channels):
    # Just as with the encoder, to create a decoder, we must first
    # allocate resources for it.  We want Python to be responsible for
    # the memory deallocation, and thus Python must be responsible for
    # the initial memory allocation.

    # The frequency must be passed in as a 32-bit int
    freq = opus.opus_int32(freq)

    # The number of channels must also be passed in as a 32-bit int
    channels = opus.opus_int32(channels)

    # Obtain the number of bytes of memory required for the decoder
    size = opus.opus_decoder_get_size(channels)

    # Allocate the required memory for the decoder
    memory = ctypes.create_string_buffer(size)

    # Cast the newly-allocated memory as a pointer to a decoder.  We
    # could also have used opus.od_p as the pointer type, but writing
    # it out in full may be clearer.
    decoder = ctypes.cast(memory, ctypes.POINTER(opus.OpusDecoder))

    # Initialise the decoder
    error = opus.opus_decoder_init(decoder, freq, channels)

    # Check that there hasn't been an error when initialising the
    # decoder
    if error != opus.OPUS_OK:
        raise Exception("An error occurred while creating the decoder: " +
                        opus.opus_strerror(error).decode("utf"))

    # Return our newly-created decoder
    return decoder
Ejemplo n.º 2
0
def encode_next_frame(source_ptr):
    global length_bytes, bytesProcessed, frame_size, frame_sizes, frame_size_index

    if bytesProcessed >= length_bytes:
        print("WARNING: No more data")
        return None

    print("Processing frame at source_ptr ", source_ptr.value)

    # Check if we have enough source data remaining to process at
    # the current frame size
    print("length_bytes: ", length_bytes)
    print("bytesProcessed: ", bytesProcessed)
    print("bytes remaining (length_bytes - bytesProcessed):",
          length_bytes - bytesProcessed)
    print("frameSizeBytes(frame_size):", frame_size_bytes(frame_size))
    while length_bytes - bytesProcessed < frame_size_bytes(frame_size):
        print("Warning! Not enough data for frame.")
        frame_size_index -= 1
        if frame_size_index < 0:
            # The data is less than the smallest number of samples
            # in a frame.  Either we ignore the remaining samples
            # and shorten the audio, or we pad the frame with
            # zeros and lengthen the audio.  We'll take the easy
            # option and shorten the audio.
            break
        frame_size = frame_sizes[frame_size_index]
        print("Decreased frame size to ", frame_size)

    if frame_size_index < 0:
        print("Warning! Ignoring samples at the end of the audio\n" +
              "as they do not fit into even the smallest frame.")
        # FIXME: This last frame probably isn't correct
        return

    # Encode the audio
    print("Encoding audio")
    numBytes = opus.opus_encode(
        encoder, ctypes.cast(source_ptr, ctypes.POINTER(opus.opus_int16)),
        frame_size, encoded_frame_ptr, max_encoded_frame_bytes)
    print("numBytes: ", numBytes)

    # Check for any errors during encoding
    if numBytes < 0:
        raise Exception("Encoder error detected: " +
                        opus.opus_strerror(numBytes).decode("utf"))

    # Move to next position in the buffer: encoder
    oldAddress = source_ptr.value
    #print("oldAddress:",oldAddress)
    deltaBytes = frame_size * source_channels * 2
    newAddress = oldAddress + deltaBytes
    #print("newAddress:",newAddress)
    source_ptr = ctypes.c_void_p(newAddress)

    bytesProcessed = source_ptr.value - source_ptr_init.value

    return (source_ptr, encoded_frame_ptr, numBytes)
Ejemplo n.º 3
0
def create_encoder(npBufSource, samples_per_second):
    # To create an encoder, we must first allocate resources for it.
    # We want Python to be responsible for the memory deallocation,
    # and thus Python must be responsible for the initial memory
    # allocation.

    # Opus can encode both speech and music, and it can automatically
    # detect when the source swaps between the two.  Here we specify
    # automatic detection.
    application = opus.OPUS_APPLICATION_AUDIO

    # The frequency must be passed in as a 32-bit int
    samples_per_second = opus.opus_int32(samples_per_second)

    # The number of channels can be obtained from the shape of the
    # NumPy array that was passed in as npBufSource
    channels = npBufSource.shape[1]

    # Obtain the number of bytes of memory required for the encoder
    size = opus.opus_encoder_get_size(channels);

    # Allocate the required memory for the encoder
    memory = ctypes.create_string_buffer(size)

    # Cast the newly-allocated memory as a pointer to an encoder.  We
    # could also have used opus.oe_p as the pointer type, but writing
    # it out in full may be clearer.
    encoder = ctypes.cast(memory, ctypes.POINTER(opus.OpusEncoder))

    # Initialise the encoder
    error = opus.opus_encoder_init(
        encoder,
        samples_per_second,
        channels,
        application
    )

    # Check that there hasn't been an error when initialising the
    # encoder
    if error != opus.OPUS_OK:
        raise Exception("An error occurred while creating the encoder: "+
                        opus.opus_strerror(error).decode("utf"))

    # Return our newly-created encoder
    return encoder
Ejemplo n.º 4
0
    def datagramReceived(self, encoded_frame, addr):
        #print("Received data from", addr)

        # TODO: Once we've started receiving data from a given
        # address, should we close down this port to other addresses?

        # Create a buffer to hold the PCM data
        pcm_buf = PCMBufType()

        # Get pointer to first element of the PCM buffer
        pcm_buf_ptr = ctypes.cast(pcm_buf, ctypes.POINTER(opus.opus_int16))

        # Get pointer to encoded frame
        encoded_frame_ptr = ctypes.cast(encoded_frame, ctypes.POINTER(ctypes.c_ubyte))
        encoded_frame_bytes = len(encoded_frame)
        
        # Decode the frame
        num_samples = opus.opus_decode(
            decoder_ptr,
            encoded_frame_ptr,
            encoded_frame_bytes,
            pcm_buf_ptr,
            samples_per_channel_in_buf,
            0 # FIXME: What's this about?
        )
        
        # Check for any errors during decoding
        if num_samples < 0:
            raise Exception("Decoder error detected: "+
                            opus.opus_strerror(numSamples).decode("utf"))

        # Create a numpy array to hold the decoded PCM data.  Note
        # that the numpy array has the correct shape (whereas the
        # original PCM buffer had sufficient space allocated for the
        # largest possible frame.
        np_pcm_buf = numpy.ctypeslib.as_array(
            pcm_buf,
            (num_samples//channels, channels)
        )
        np_pcm_buf = np_pcm_buf[0:num_samples]
        
        # Put the samples on the queue to play
        q.put_nowait(np_pcm_buf)
Ejemplo n.º 5
0
    def datagramReceived(self, data, addr):
        print("Received data from", addr)

        print("len(data):", len(data))
        #print(data)

        # Create a buffer to hold the PCM data
        buf = TypeBuf()

        # Get pointer to first element of buf
        bufPtr = ctypes.cast(buf, ctypes.POINTER(opus.opus_int16))

        # Get pointer to encoded frame
        encodedFramePtr = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte))
        numBytes = len(data)

        # Decode the frame
        numSamples = opus.opus_decode(
            decoderPtr,
            encodedFramePtr,
            numBytes,
            bufPtr,
            samples_per_channel_in_buf,
            0  # FIXME: What's this about?
        )
        print("numSamples: ", numSamples)

        # Check for any errors during decoding
        if numSamples < 0:
            raise Exception("Decoder error detected: " +
                            opus.opus_strerror(numSamples).decode("utf"))

        # Put the samples on the queue to play
        np_buf = numpy.ctypeslib.as_array(buf,
                                          (numSamples // channels, channels))

        np_buf = np_buf[0:numSamples]

        print("data placed on queue")
        #print("channels:", channels)
        print("shape:", np_buf.shape)
        q.put_nowait(np_buf)
Ejemplo n.º 6
0
    def write_opus(self, output_filename):
        # Go through the frames and save them as an OggOpus file

        # Create a new stream state with a random serial number
        stream_state = opus_helpers.create_stream_state()

        # Create a packet (reused for each pass)
        ogg_packet = ogg.ogg_packet()

        # Flag to indicate the start of stream
        start_of_stream = 1

        # Packet counter
        count_packets = 0

        # PCM samples counter
        count_samples = 0

        # Allocate memory for a page
        ogg_page = ogg.ogg_page()

        # Allocate storage space for the encoded frame.  4,000 bytes
        # is the recommended maximum buffer size for the encoded
        # frame.
        max_bytes_in_encoded_frame = opus.opus_int32(4000)
        EncodedFrameType = ctypes.c_ubyte * max_bytes_in_encoded_frame.value
        encoded_frame = EncodedFrameType()

        # Create a pointer to the first byte of the buffer for the
        # encoded frame.
        encoded_frame_ptr = ctypes.cast(
            ctypes.pointer(encoded_frame),
            ctypes.POINTER(ctypes.c_ubyte)
        )

        
        # Open file for writing
        f = open(output_filename, "wb")

        # Headers
        # =======
        
        # Specify the identification header
        id_header = opus.make_identification_header(
            pre_skip = self._pre_skip
        )

        # Specify the packet containing the identification header
        ogg_packet.packet = ctypes.cast(id_header, ogg.c_uchar_p)
        ogg_packet.bytes = len(id_header)
        ogg_packet.b_o_s = start_of_stream
        ogg_packet.e_o_s = 0
        ogg_packet.granulepos = 0
        ogg_packet.packetno = count_packets
        start_of_stream = 0
        count_packets += 1

        # Write the header
        result = ogg.ogg_stream_packetin(
            stream_state,
            ogg_packet
        )

        if result != 0:
            raise Exception("Failed to write Opus identification header")


        # Specify the comment header
        comment_header = opus.make_comment_header()

        # Specify the packet containing the identification header
        ogg_packet.packet = ctypes.cast(comment_header, ogg.c_uchar_p)
        ogg_packet.bytes = len(comment_header)
        ogg_packet.b_o_s = start_of_stream
        ogg_packet.e_o_s = 0
        ogg_packet.granulepos = 0
        ogg_packet.packetno = count_packets
        count_packets += 1

        # Write the header
        result = ogg.ogg_stream_packetin(
            stream_state,
            ogg_packet
        )

        if result != 0:
            raise Exception("Failed to write Opus comment header")


        # Write out pages to file
        while ogg.ogg_stream_flush(ctypes.pointer(stream_state),
                                   ctypes.pointer(ogg_page)) != 0:
            # Write page
            print("Writing header page")
            f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
            f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

            
        # Frames
        # ======

        # Loop through the PCM frames in the queue
        while not q.empty():
            # Get the frame from the queue
            frame_pcm = q.get_nowait()

            # Convert to opus_int16
            frame_pcm = numpy.array(frame_pcm * 2**15, dtype=opus.opus_int16) 

            # Create a pointer to the start of the frame's data
            source_ptr = frame_pcm.ctypes.data_as(ctypes.c_void_p)
            
            #print("Processing frame at sourcePtr ", sourcePtr.value)

            # Check if we have enough source data remaining to process at
            # the current frame size
            samples_per_frame = 960
            assert len(frame_pcm) == samples_per_frame

            # Encode the audio
            #print("Encoding audio")
            num_bytes = opus.opus_encode(
                self._encoder,
                ctypes.cast(source_ptr, ctypes.POINTER(opus.opus_int16)),
                samples_per_frame,
                encoded_frame_ptr,
                max_bytes_in_encoded_frame
            )
            #print("num_bytes: ", num_bytes)

            # Check for any errors during encoding
            if num_bytes < 0:
                raise Exception("Encoder error detected: "+
                                opus.opus_strerror(num_bytes).decode("utf"))

            # Writing OggOpus
            # ===============

            # Increase the number of samples
            count_samples += samples_per_frame

            # Place data into the packet
            ogg_packet.packet = encoded_frame_ptr
            ogg_packet.bytes = num_bytes
            ogg_packet.b_o_s = start_of_stream
            ogg_packet.e_o_s = 0 # FIXME: It needs to end!
            ogg_packet.granulepos = count_samples
            ogg_packet.packetno = count_packets

            # No longer the start of stream
            start_of_stream = 0

            # Increase the number of packets
            count_packets += 1

            # Place the packet in to the stream
            result = ogg.ogg_stream_packetin(
                stream_state,
                ogg_packet
            )

            # Check for errors
            if result != 0:
                raise Exception("Error while placing packet in Ogg stream")

            # Write out pages to file
            while ogg.ogg_stream_pageout(ctypes.pointer(stream_state),
                                         ctypes.pointer(ogg_page)) != 0:
                # Write page
                print("Writing page")
                f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
                f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

                
        # Force the writing of the final page
        while ogg.ogg_stream_flush(ctypes.pointer(stream_state),
                                   ctypes.pointer(ogg_page)) != 0:
            # Write page
            print("Writing final page")
            f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
            f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

                
        # Make sure the queue is empty
        if not q.empty():
            print("WARNING: Failed to completely process all the recorded frames")
        
        # Finished
        f.close()
        print("Finished writing file")
Ejemplo n.º 7
0
    def process_frame(self, encoded_frame, seq_no):
        # Aquire the lock to ensure that we're threadsafe
        with self.__lock:
            # Mark the fact that we've started
            self.__started = True

            # Is the sequence number the one we're expecting?  If not,
            # consider storing it for later processing.
            print("Expecting seq no", self.__expected_seq_no)
            if encoded_frame is None:
                print("Did not get seq no", seq_no)
            else:
                print("Got seq no", seq_no)

            if seq_no != self.__expected_seq_no:
                print("Found out of order frame")
                # Calculate the new frame's distance from the
                # currently expected sequence number.  Because the
                # sequence numbers may roll over, we need to account
                # for that.
                distance = JitterBuffer.__calc_distance(
                    seq_no, self.__expected_seq_no, JitterBuffer.seq_rollover)

                # Check if the frame is too late
                if distance > 0:
                    print("Frame is ahead of what we were expecting; storing")
                    self.__out_of_order_frames[seq_no] = encoded_frame
                else:
                    print("Frame is behind what we were expecting; discarding")
                    pass

                # Are there frames still in the queue?  If not, then we
                # have a problem.  The current frame isn't what we need,
                # and we're in immediate need of the one we're missing.
                # It's time to give up on the currently expected frame.
                if q.empty():
                    self.give_up_on_expected_sequence()

                return

            # The encoded frame is the next in the sequence, so process it
            # now.

            # Create a buffer to hold the PCM data
            pcm_buf = self.__PCMFrameBufferType()

            # Get pointer to first element of the PCM buffer
            pcm_buf_ptr = ctypes.cast(pcm_buf, ctypes.POINTER(opus.opus_int16))

            # Get pointer to encoded frame
            if encoded_frame is None:
                encoded_frame_ptr = None
                encoded_frame_bytes = 0
            else:
                encoded_frame_ptr = ctypes.cast(encoded_frame,
                                                ctypes.POINTER(ctypes.c_ubyte))
                encoded_frame_bytes = len(encoded_frame)

            # Decode the frame
            num_samples = opus.opus_decode(
                self.__decoder_ptr,
                encoded_frame_ptr,
                encoded_frame_bytes,
                pcm_buf_ptr,
                self.__samples_per_channel_in_buf,
                0  # FIXME: What's this about?
            )

            # Check for any errors during decoding
            if num_samples < 0:
                raise Exception("Decoder error detected: " +
                                opus.opus_strerror(numSamples).decode("utf"))

            # Create a numpy array to hold the decoded PCM data.  Note
            # that the numpy array has the correct shape (whereas the
            # original PCM buffer had sufficient space allocated for the
            # largest possible frame.
            np_pcm_buf = numpy.ctypeslib.as_array(
                pcm_buf, (num_samples // channels, channels))
            np_pcm_buf = np_pcm_buf[0:num_samples]

            # Put the samples on the queue to play
            q.put_nowait(np_pcm_buf)

            # We can now expect the next sequence number
            self.__expected_seq_no += 1

            # If the next frame is already in the out-of-order dictionary,
            # process it now
            seq_no = self.__expected_seq_no
            if (seq_no in self.__out_of_order_frames):
                print("Processing previously stored, out-of-order, frame")
                self.process_frame(self.__out_of_order_frames[seq_no], seq_no)
                del self.__out_of_order_frames[seq_no]
Ejemplo n.º 8
0
def encodeThenDecode(npBufSource, npBufTarget, freq):
    # Encoding
    # ========

    # Extract the number of channels in the source
    sourceChannels = npBufSource.shape[1]

    # Create an encoder
    encoder = createEncoder(npBufSource, freq)

    # Frame sizes are measured in number of samples.  There are only a
    # specified number of possible valid frame durations for Opus,
    # which (assuming a frequency of 48kHz) gives the following valid
    # sizes.
    frameSizes = [120, 240, 480, 960, 1920, 2880]

    # Specify the desired frame size.  This will be used for the vast
    # majority of the encoding, except possibly at the end of the
    # buffer (as there may not be sufficient data left to fill a
    # frame.)
    frameSizeIndex = 5
    frameSize = frameSizes[frameSizeIndex]

    # Function to calculate the size of a frame in bytes
    def frameSizeBytes(frameSize):
        global bytesPerSample
        return frameSize * sourceChannels * bytesPerSample

    # Allocate storage space for the encoded frame.  4,000 bytes is
    # the recommended maximum buffer size for the encoded frame.
    maxEncodedFrameBytes = opus.opus_int32(4000)
    encodedFrameType = ctypes.c_ubyte * maxEncodedFrameBytes.value
    encodedFrame = encodedFrameType()

    # Create a pointer to the first byte of the buffer for the encoded
    # frame.
    encodedFramePtr = ctypes.cast(ctypes.pointer(encodedFrame),
                                  ctypes.POINTER(ctypes.c_ubyte))

    # Number of bytes to process in buffer
    bytesPerSample = 2
    lengthBytes = buf.shape[0] * buf.shape[1] * bytesPerSample

    # Saving
    # ======

    # Create a new stream state with a random serial number
    stream_state = createStreamState()

    # Create a packet (reused for each pass)
    ogg_packet = ogg.ogg_packet()

    # Flag to indicate the start of stream
    start_of_stream = 1

    # Packet counter
    count_packets = 0

    # PCM samples counter
    count_samples = 0

    # Allocate memory for a page
    ogg_page = ogg.ogg_page()

    # Open file for writing
    output_filename = "test.opus"
    f = open(output_filename, "wb")

    # Specify the identification header
    id_header = opus.make_identification_header(pre_skip=312)

    # Specify the packet containing the identification header
    ogg_packet.packet = ctypes.cast(id_header, ogg.c_uchar_p)
    ogg_packet.bytes = len(id_header)
    ogg_packet.b_o_s = start_of_stream
    ogg_packet.e_o_s = 0
    ogg_packet.granulepos = 0
    ogg_packet.packetno = count_packets
    start_of_stream = 0
    count_packets += 1

    # Write the header
    result = ogg.ogg_stream_packetin(stream_state, ogg_packet)

    if result != 0:
        raise Exception("Failed to write Opus identification header")

    # Specify the comment header
    comment_header = opus.make_comment_header()

    # Specify the packet containing the identification header
    ogg_packet.packet = ctypes.cast(comment_header, ogg.c_uchar_p)
    ogg_packet.bytes = len(comment_header)
    ogg_packet.b_o_s = start_of_stream
    ogg_packet.e_o_s = 0
    ogg_packet.granulepos = 0
    ogg_packet.packetno = count_packets
    count_packets += 1

    # Write the header
    result = ogg.ogg_stream_packetin(stream_state, ogg_packet)

    if result != 0:
        raise Exception("Failed to write Opus comment header")

    # Write out pages to file
    while ogg.ogg_stream_flush(ctypes.pointer(stream_state),
                               ctypes.pointer(ogg_page)) != 0:
        # Write page
        print("Writing page")
        f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
        f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

    # Decoding
    # ========

    # Extract the number of channels for the target
    targetChannels = npBufTarget.shape[1]

    # Create a decoder
    decoderFreq = 48000  # TODO: Test changes to this
    decoderPtr = createDecoder(decoderFreq, targetChannels)

    # Encode and re-decode the audio
    # ==============================

    # Pointer to a location in the source buffer.  We will increment
    # this as we progress through the encoding of the buffer.  It
    # starts pointing to the first byte.
    sourcePtr = npBufSource.ctypes.data_as(ctypes.c_void_p)
    sourcePtr_init = sourcePtr

    # The number of bytes processed will be the difference between the
    # pointer's current location and the address of the first byte.
    bytesProcessed = sourcePtr.value - sourcePtr_init.value

    # Pointer to a location in the target buffer.  We will increment
    # this as we progress through re-decoding each encoded frame.
    targetPtr = npBufTarget.ctypes.data_as(ctypes.c_void_p)

    # Loop through the source buffer
    while bytesProcessed < lengthBytes:
        print("Processing frame at sourcePtr ", sourcePtr.value)

        # Check if we have enough source data remaining to process at
        # the current frame size
        print("lengthBytes: ", lengthBytes)
        print("bytesProcessed: ", bytesProcessed)
        print("bytes remaining (lengthBytes - bytesProcessed):",
              lengthBytes - bytesProcessed)
        print("frameSizeBytes(frameSize):", frameSizeBytes(frameSize))
        while lengthBytes - bytesProcessed < frameSizeBytes(frameSize):
            print("Warning! Not enough data for frame.")
            frameSizeIndex -= 1
            if frameSizeIndex < 0:
                # The data is less than the smallest number of samples
                # in a frame.  Either we ignore the remaining samples
                # and shorten the audio, or we pad the frame with
                # zeros and lengthen the audio.  We'll take the easy
                # option and shorten the audio.
                break
            frameSize = frameSizes[frameSizeIndex]
            print("Decreased frame size to ", frameSize)

        if frameSizeIndex < 0:
            print("Warning! Ignoring samples at the end of the audio\n" +
                  "as they do not fit into even the smallest frame.")
            break

        # Encode the audio
        print("Encoding audio")
        numBytes = opus.opus_encode(
            encoder, ctypes.cast(sourcePtr, ctypes.POINTER(opus.opus_int16)),
            frameSize, encodedFramePtr, maxEncodedFrameBytes)
        print("numBytes: ", numBytes)

        # Check for any errors during encoding
        if numBytes < 0:
            raise Exception("Encoder error detected: " +
                            opus.opus_strerror(numBytes).decode("utf"))

        # Move to next position in the buffer: encoder
        oldAddress = sourcePtr.value
        #print("oldAddress:",oldAddress)
        deltaBytes = frameSize * sourceChannels * 2
        newAddress = oldAddress + deltaBytes
        #print("newAddress:",newAddress)
        sourcePtr = ctypes.c_void_p(newAddress)

        bytesProcessed = sourcePtr.value - sourcePtr_init.value

        # Writing OggOpus
        # ===============

        # Increase the number of samples
        count_samples += frameSize

        # Place data into the packet
        ogg_packet.packet = encodedFramePtr
        ogg_packet.bytes = numBytes
        ogg_packet.b_o_s = start_of_stream
        ogg_packet.e_o_s = 0  # FIXME: It needs to end!
        ogg_packet.granulepos = count_samples
        ogg_packet.packetno = count_packets

        # No longer the start of stream
        start_of_stream = 0

        # Increase the number of packets
        count_packets += 1

        # Place the packet in to the stream
        result = ogg.ogg_stream_packetin(stream_state, ogg_packet)

        # Check for errors
        if result != 0:
            raise Exception("Error while placing packet in Ogg stream")

        # Write out pages to file
        while ogg.ogg_stream_pageout(ctypes.pointer(stream_state),
                                     ctypes.pointer(ogg_page)) != 0:
            # Write page
            print("Writing page")
            f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
            f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

        # Decode the audio
        if True:
            print("Decoding audio")
            numSamples = opus.opus_decode(
                decoderPtr,
                encodedFramePtr,
                numBytes,
                ctypes.cast(targetPtr, ctypes.POINTER(ctypes.c_short)),
                5760,  # Max space required in PCM
                0  # What's this about?
            )
            print("numSamples: ", numSamples)

            # Check for any errors during decoding
            if numSamples < 0:
                raise Exception("Decoder error detected: " +
                                opus.opus_strerror(numSamples).decode("utf"))

            # Move to next position in the buffer: decoder
            targetPtr.value += numSamples * targetChannels * 2

    # Write a packet saying we're at the end of the stream
    # Place data into the packet
    ogg_packet.packet = None
    ogg_packet.bytes = 0
    ogg_packet.b_o_s = start_of_stream
    ogg_packet.e_o_s = 1
    ogg_packet.granulepos = count_samples
    ogg_packet.packetno = count_packets

    # Increase the number of packets
    count_packets += 1

    # Place the packet in to the stream
    #result = ogg.ogg_stream_packetin(
    #    stream_state,
    #    ogg_packet
    #)

    # Check for errors
    if result != 0:
        raise Exception("Error while placing packet in Ogg stream")

    # Write out pages to file
    while ogg.ogg_stream_pageout(ctypes.pointer(stream_state),
                                 ctypes.pointer(ogg_page)) != 0:
        # Write page
        print("Writing page")
        f.write(bytes(ogg_page.header[0:ogg_page.header_len]))
        f.write(bytes(ogg_page.body[0:ogg_page.body_len]))

    # Close file
    f.close()
Ejemplo n.º 9
0
def encodeThenDecode(npBufSource, npBufTarget, freq):
    # Encoding
    # ========

    # Extract the number of channels in the source
    sourceChannels = npBufSource.shape[1]

    # Create an encoder
    encoder = createEncoder(npBufSource, freq)

    # Frame sizes are measured in number of samples.  There are only a
    # specified number of possible valid frame durations for Opus,
    # which (assuming a frequency of 48kHz) gives the following valid
    # sizes.
    frameSizes = [120, 240, 480, 960, 1920, 2880]

    # Specify the desired frame size.  This will be used for the vast
    # majority of the encoding, except possibly at the end of the
    # buffer (as there may not be sufficient data left to fill a
    # frame.)
    frameSizeIndex = 5
    frameSize = frameSizes[frameSizeIndex]

    # Function to calculate the size of a frame in bytes
    def frameSizeBytes(frameSize):
        global bytesPerSample
        return frameSize * sourceChannels * bytesPerSample

    # Allocate storage space for the encoded frame.  4,000 bytes is
    # the recommended maximum buffer size for the encoded frame.
    maxEncodedFrameBytes = opus.opus_int32(4000)
    encodedFrameType = ctypes.c_ubyte * maxEncodedFrameBytes.value
    encodedFrame = encodedFrameType()

    # Create a pointer to the first byte of the buffer for the encoded
    # frame.
    encodedFramePtr = ctypes.cast(ctypes.pointer(encodedFrame),
                                  ctypes.POINTER(ctypes.c_ubyte))

    # Number of bytes to process in buffer
    bytesPerSample = 2
    lengthBytes = buf.shape[0] * buf.shape[1] * bytesPerSample

    # Decoding
    # ========

    # Extract the number of channels for the target
    targetChannels = npBufTarget.shape[1]

    # Create a decoder
    decoderFreq = 48000  # TODO: Test changes to this
    decoderPtr = createDecoder(decoderFreq, targetChannels)

    # Encode and re-decode the audio
    # ==============================

    # Pointer to a location in the source buffer.  We will increment
    # this as we progress through the encoding of the buffer.  It
    # starts pointing to the first byte.
    sourcePtr = npBufSource.ctypes.data_as(ctypes.c_void_p)
    sourcePtr_init = sourcePtr

    # The number of bytes processed will be the difference between the
    # pointer's current location and the address of the first byte.
    bytesProcessed = sourcePtr.value - sourcePtr_init.value

    # Pointer to a location in the target buffer.  We will increment
    # this as we progress through re-decoding each encoded frame.
    targetPtr = npBufTarget.ctypes.data_as(ctypes.c_void_p)

    # Loop through the source buffer
    count = 0  # FIXME: debugging only
    while bytesProcessed < lengthBytes:
        print("processing frame: ", count)
        count += 1
        print("Processing frame at sourcePtr ", sourcePtr.value)

        # Check if we have enough source data remaining to process at
        # the current frame size
        print("lengthBytes: ", lengthBytes)
        print("bytesProcessed: ", bytesProcessed)
        print("bytes remaining (lengthBytes - bytesProcessed):",
              lengthBytes - bytesProcessed)
        print("frameSizeBytes(frameSize):", frameSizeBytes(frameSize))
        while lengthBytes - bytesProcessed < frameSizeBytes(frameSize):
            print("Warning! Not enough data for frame.")
            frameSizeIndex -= 1
            if frameSizeIndex < 0:
                # The data is less than the smallest number of samples
                # in a frame.  Either we ignore the remaining samples
                # and shorten the audio, or we pad the frame with
                # zeros and lengthen the audio.  We'll take the easy
                # option and shorten the audio.
                break
            frameSize = frameSizes[frameSizeIndex]
            print("Decreased frame size to ", frameSize)

        if frameSizeIndex < 0:
            print("Warning! Ignoring samples at the end of the audio\n" +
                  "as they do not fit into even the smallest frame.")
            break

        # Encode the audio
        if True:
            # Print out the PCM data to see that it's readable
            #p = sourcePtr
            #d = 0
            #while d < frameSize*2*2:
            #    p2 = ctypes.c_void_p(ctypes.cast(p,ctypes.c_void_p).value + d)
            #    print("p[",p2.value,"]:",ctypes.cast(p2, opus.opus_int16_p).contents.value)
            #    d += 2

            print("Encoding audio")
            numBytes = opus.opus_encode(
                encoder, ctypes.cast(sourcePtr,
                                     ctypes.POINTER(opus.opus_int16)),
                frameSize, encodedFramePtr, maxEncodedFrameBytes)
            print("numBytes: ", numBytes)

            # Check for any errors during encoding
            if numBytes < 0:
                raise Exception("Encoder error detected: " +
                                opus.opus_strerror(numBytes).decode("utf"))

            # Move to next position in the buffer: encoder
            oldAddress = sourcePtr.value
            #print("oldAddress:",oldAddress)
            deltaBytes = frameSize * sourceChannels * 2
            newAddress = oldAddress + deltaBytes
            #print("newAddress:",newAddress)
            sourcePtr = ctypes.c_void_p(newAddress)

            bytesProcessed = sourcePtr.value - sourcePtr_init.value

        # Decode the audio
        if True:
            print("Decoding audio")
            numSamples = opus.opus_decode(
                decoderPtr,
                encodedFramePtr,
                numBytes,
                ctypes.cast(targetPtr, ctypes.POINTER(ctypes.c_short)),
                5760,  # Max space required in PCM
                0  # What's this about?
            )
            print("numSamples: ", numSamples)

            # Check for any errors during decoding
            if numSamples < 0:
                raise Exception("Decoder error detected: " +
                                opus.opus_strerror(numSamples).decode("utf"))

            # Move to next position in the buffer: decoder
            targetPtr.value += numSamples * targetChannels * 2