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)
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)
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()
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]
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