#
#		THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE
#		THE 'COPYING' FILE FOR DETAILS
#
#		AudioBackend.py
#
#		This file offers functions to retrieve information about the
#		audio devices available with the current audio backend, and
#		the capabilities that those devices support.
#
#-------------------------------------------------------------------------------

import gst, gtk, gobject
import time
import Globals

#=========================================================================

def ListPlaybackDevices(sink=None, probe_name=True):
	if not sink:
		# get preference from Globals
		sink = Globals.settings.playback["audiosink"]
		
	try:
		bin = gst.parse_bin_from_description(sink, False)
	except (gobject.GError, gst.ElementNotFoundError):
		Globals.debug("Cannot list playback devices: cannot parse bin", sink)
		return list()
	
	try:
		element = bin.sinks().next()
	except StopIteration:
		Globals.debug("Cannot list playback devices: no sink device in the bin", sink)
		
	return ListDeviceProbe(element, probe_name)
	
#_____________________________________________________________________
	
def ListCaptureDevices(src=None, probe_name=True):
	if not src:
		# get preference from Globals
		src = Globals.settings.recording["audiosrc"]
	try:
		bin = gst.parse_bin_from_description(src, False)
	except (gobject.GError, gst.ElementNotFoundError):
		Globals.debug("Cannot list capture devices: cannot parse bin", src)
		return list()
	
	try:
		element = bin.iterate_sources().next()
	except StopIteration:
		Globals.debug("Cannot list capture devices: no source device in the bin", src)
		
	return ListDeviceProbe(element, probe_name)
	
#_____________________________________________________________________

def ListDeviceProbe(element, probe_name):
	element_name = element.get_factory().get_property("name")
	dev_info_list = []
	
	if hasattr(element.props, "device"):
		default_device = element.__class__.props.device.default_value
		if gobject.type_is_a(element, gst.interfaces.PropertyProbe):
			element.probe_property_name("device")
			devices = element.probe_get_values_name("device")
	
			if (default_device != None) and (default_device not in devices):
				dev_info_list.append((default_device, ""))
			
			if probe_name and hasattr(element.props, "device-name"):
				for dev in devices:
					element.set_property("device", dev)
					
					# certain elements like pulsesrc won't load the device-name until STATE_PLAYING
					state_change_type = element.set_state(gst.STATE_PLAYING)
					if state_change_type == gst.STATE_CHANGE_ASYNC:
						new_state = None
						while new_state != gst.STATE_PLAYING and new_state != gst.STATE_READY:
							gtk.main_iteration()
							state_change_type, new_state, pending = element.get_state(0)
							time.sleep(0.01)
					name = element.get_property("device-name")
					element.set_state(gst.STATE_NULL)
					
					dev_info_list.append((dev,name))
			else:
				for dev in devices:
					dev_info_list.append((dev,""))
		else:
			dev_info_list.append((default_device, ""))
	else:
		Globals.debug("Cannot list devices: property probe not supported on", element_name)
		
	return dev_info_list

#_____________________________________________________________________

def GetRecordingSampleRate(device=None):
	""" 
	Checks for available recording sample rates.
	
	Parameters:
		device -- Backend dependent device to poll for values.
	
	Returns:
		any of the following depending on the sound card:
		1) an int representing the only supported sample rate.
		2) an IntRange class with IntRange.low and IntRange.high being the min and max sample rates.
		3) a list of ints representing all the supported sample rates.
	"""
	
	src = Globals.settings.recording["audiosrc"]
	try:
		bin = gst.parse_bin_from_description(src, False)
	except (gobject.GError, gst.ElementNotFoundError):
		Globals.debug("Cannot get sample rate: cannot parse bin", src)
		return list()
	
	try:
		element = bin.iterate_sources().next()
	except StopIteration:
		Globals.debug("Cannot get sample rate: no source device in the bin", src)
		return list()
	
	if device:
		element.set_property("device", device)

	# open device (so caps are probed)
	bin.set_state(gst.STATE_PAUSED)

	try:
		pad = element.src_pads().next()
		caps = pad.get_caps()
		val = caps[0]["rate"]
	except:
		val = None
		
	# clean up
	bin.set_state(gst.STATE_NULL)
	del element, bin
	
	return val

#_____________________________________________________________________

def GetChannelsOffered(device):
	"""
	Checks for the number of available channels on a device.
	
	Parameters:
		device -- ALSA device (e.g. hw:0) to poll for available channels.
		
	Returns:
		the number of channels available on a device.
	"""
	src_desc = Globals.settings.recording["audiosrc"]
	try:
		bin = gst.parse_bin_from_description(src_desc, True)
	except (gobject.GError, gst.ElementNotFoundError):
		Globals.debug("Cannot get number of channels: cannot parse bin", src_desc)
		return 0
	
	try:
		src = bin.iterate_sources().next()
	except StopIteration:
		Globals.debug("Cannot list capture devices: no source device in the bin", src_desc)

	fakesink = gst.element_factory_make("fakesink")
	pipeline = gst.Pipeline()
	pipeline.add(bin)
	pipeline.add(fakesink)
	bin.link(fakesink)

	src.set_property("device", device)
	pipeline.set_state(gst.STATE_PAUSED)
	# block for *at most* 100ms to wait for state to change 
	pipeline.get_state(gst.SECOND / 100)
	
	try:
		#Assume the card only offers one src (we can't handle more anyway)
		for pad in src.src_pads():
			caps = pad.get_negotiated_caps()
			# in case it hasn't negotiated yet, get standard caps which are less precise.
			if not caps:
				Globals.debug("GetChannelsOffered(): Waited for STATE_PAUSED, but still no negotiated caps on", device)
				caps = pad.get_caps()
	except:
		Globals.debug("Couldn't get source pad for %s"%device)
		pipeline.set_state(gst.STATE_NULL)
		return 0
	
	nums = []
	for struct in caps:
		channels = caps[0]["channels"]
		if isinstance(channels, gst.IntRange):
			if channels.high > 20000:
				#Assume we're being given the max number of channels for gstreamer, so take low number
				nums.append(channels.low)
			else:
				#Otherwise take the high number
				nums.append(channels.high)
		else:
			nums.append(channels)

	numChannels = max(nums)
	Globals.debug("Detected channels = %s from caps:" % numChannels, caps.to_string())

	if numChannels > 8:
		# audioconvert can't handle more than 8 channels
		numChannels = 8

	#if numChannels == 2:
		#Assume one stereo input
	#	numChannels = 1

	pipeline.set_state(gst.STATE_NULL)
	return numChannels

"""
The following function, is meant for testing this file independantly from the rest.
"""
def print_device_list(l):
	for dev, name in l:
		print " " + repr(dev)
		print "  => " + repr(name)

if __name__ == "__main__":
	print "Listing ALSA capture:"
	print_device_list(ListCaptureDevices("alsasrc", True))
		
	print "Listing ALSA output:"
	print_device_list(ListPlaybackDevices("alsasink", True))
		
	print "Listing PulseAudio capture:"
	print_device_list(ListCaptureDevices("pulsesrc", True))
	
	print "Listing PulseAudio output:"
	print_device_list(ListPlaybackDevices("pulsesink", True))
