#!/usr/bin/env python
# Convert between network and filesystem views of AMR files
# E. Woudenberg, Lobby7 Feb 2003
# Tested under WinXP/Cygwin

import os, sys, struct, array

def usage():
	print '''This program converts between:
  .amr files [AMR File Storage Format (draft-ietf-avt-rtp-amr-10.txt, Sec. 6.2)]
	and
  .cod files [AMR Interface Format 2 (3GPP TS 26.101, Annex A)]

Usage: amrconv.py filename.ext
	When .ext == '.amr' creates filename.ext.cod file
	When .ext == '.cod' creates filename.ext.amr file
	'''
	sys.exit(1)
	
class nibble_reader:
	def __init__(self, data):
		self.data = array.array('B', data)
		self.i = 0
	def read(self):
		if self.i/2 >= len(self.data):
			return None
		c = self.data[self.i/2]
		if self.i % 2:
			c >>= 4
		c &= 0xF
		self.i += 1
		return c

class nibble_writer:
	def __init__(self):
		self.out = array.array('B')
		self.i = 0
	def write(self, n):
		if self.i % 2:
			self.out[self.i/2] |= n<<4;
		else:
			self.out.append(n)
		self.i += 1
	def tostring(self):
		return self.out.tostring()
		
def makefliptab():
	global fliptab
	fliptab = []
	for i in range(256):
		fliptab.append(bytflip(i))

def bytflip(n):
	return nybflip(n>>4) + (nybflip(n&0xf)<<4)

def nybflip(n):
	f = ((n&1)<<3) + ((n&2)<<1) + ((n&4)>>1) + ((n&8)>>3)
#	print '%d%d%d%d' % ((f&8!=0),(f&4!=0),(f&2!=0),(f&1!=0))
	return f

# AMR file has bits in Network Bit Order, which is most significant bit
# first. Here we mirror them to restore their order.

def flip_bitorder(b):
	a = array.array('B',b)
	for i in range(len(a)):
		a[i] = fliptab[a[i]]
	return a.tostring()

Framebits = [95,103,118,134,148,159,204,244]

def amr2if2(inf, outf):
	f = open(inf,'rb')
	fw = open(outf, 'wb')
	
	head = f.read(6)
	if head != '#!AMR\n':
		print 'not an #!AMR file'
		sys.exit(1)

	frameno = 0
	while 1:
		ft = f.read(1)
		if len(ft) == 0: break
		fb = struct.unpack('B', ft)[0]
		if not (fb & 4):
			print 'invalid frame at frameno %d, exiting' % frameno
			sys.exit(1)
		ftype = (fb >> 3) & 0xF
		fsize = (Framebits[ftype]+7)/8
		# print 'frametype', ftype, 'framesize', fsize
		frame = f.read(fsize)
		nr = nibble_reader(flip_bitorder(frame))
		nw = nibble_writer()
		nw.write(ftype)
		while 1:
			n = nr.read()
			if n == None: break
			nw.write(n)
		outfsize = (Framebits[ftype]+4+7)/8 # round to byte count, include ftype nibble
		fw.write(nw.tostring()[:outfsize])
		frameno += 1
	print 'Processed %d frames, created %s' % (frameno, outf)

def if22amr(inf, outf):
	f = open(inf,'rb')
	fw = open(outf, 'wb')
	
	fw.write('#!AMR\n')
	frameno = 0
	while 1:
		ft = f.read(1)
		if len(ft) == 0: break
		fb = struct.unpack('B', ft)[0]
		ftype = fb&0xF
		fsize = (Framebits[ftype]+4+7)/8
		# print 'frametype', ftype, 'framesize', fsize
		frame = f.read(fsize-1)
		nr = nibble_reader(ft + frame)
		nr.read() # skip frame type 
		nw = nibble_writer()
		while 1:
			n = nr.read()
			if n == None: break
			nw.write(n)
		fw.write(struct.pack('B',(ftype<<3) + 4)) # add "valid"
		outfsize = (Framebits[ftype]+7)/8
		fw.write(flip_bitorder(nw.tostring()[:outfsize]))
		frameno += 1
	print 'Processed %d frames, created %s' % (frameno, outf)

def main():
	if len(sys.argv) != 2:
		usage()
	makefliptab()
	file = sys.argv[1]
	path, ext = os.path.splitext(file)
	if ext == '.cod':
		if22amr(file, file+'.amr')
	elif ext == '.amr':
		amr2if2(file, file+'.cod')
	else:
		print "File extension not .amr or .cod, don't know what to do!\n"
		usage()

main()
