#! /usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2016 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """MPEG SA3D box processing classes. Enables the injection of an SA3D MPEG-4. The SA3D box specification conforms to that outlined in docs/spatial-audio-rfc.md """ import struct from spatialmedia.mpeg import box from spatialmedia.mpeg import constants def load(fh, position=None, end=None): """ Loads the SA3D box located at position in an mp4 file. Args: fh: file handle, input file handle. position: int or None, current file position. Returns: new_box: box, SA3D box loaded from the file location or None. """ if position is None: position = fh.tell() fh.seek(position) new_box = SA3DBox() new_box.position = position size = struct.unpack(">I", fh.read(4))[0] name = fh.read(4) if (name != constants.TAG_SA3D): print("Error: box is not an SA3D box.") return None if (position + size > end): print("Error: SA3D box size exceeds bounds.") return None new_box.content_size = size - new_box.header_size new_box.version = struct.unpack(">B", fh.read(1))[0] new_box.ambisonic_type = struct.unpack(">B", fh.read(1))[0] new_box.head_locked_stereo = (new_box.ambisonic_type & int('10000000', 2) != 0) new_box.ambisonic_type = new_box.ambisonic_type & int('01111111', 2) new_box.ambisonic_order = struct.unpack(">I", fh.read(4))[0] new_box.ambisonic_channel_ordering = struct.unpack(">B", fh.read(1))[0] new_box.ambisonic_normalization = struct.unpack(">B", fh.read(1))[0] new_box.num_channels = struct.unpack(">I", fh.read(4))[0] for i in range(0, new_box.num_channels): new_box.channel_map.append( struct.unpack(">I", fh.read(4))[0]) return new_box class SA3DBox(box.Box): ambisonic_types = {'periphonic': 0} ambisonic_orderings = {'ACN': 0} ambisonic_normalizations = {'SN3D': 0} def __init__(self): box.Box.__init__(self) self.name = constants.TAG_SA3D self.header_size = 8 self.version = 0 self.ambisonic_type = 0 self.head_locked_stereo = False self.ambisonic_order = 0 self.ambisonic_channel_ordering = 0 self.ambisonic_normalization = 0 self.num_channels = 0 self.channel_map = list() @staticmethod def create(num_channels, audio_metadata): new_box = SA3DBox() new_box.header_size = 8 new_box.name = constants.TAG_SA3D new_box.version = 0 # uint8 new_box.content_size += 1 # uint8 new_box.ambisonic_type = SA3DBox.ambisonic_types[ audio_metadata["ambisonic_type"]] new_box.head_locked_stereo = audio_metadata["head_locked_stereo"] new_box.content_size += 1 # uint8 new_box.ambisonic_order = audio_metadata["ambisonic_order"] new_box.content_size += 4 # uint32 new_box.ambisonic_channel_ordering = SA3DBox.ambisonic_orderings[ audio_metadata["ambisonic_channel_ordering"]] new_box.content_size += 1 # uint8 new_box.ambisonic_normalization = SA3DBox.ambisonic_normalizations[ audio_metadata["ambisonic_normalization"]] new_box.content_size += 1 # uint8 new_box.num_channels = num_channels new_box.content_size += 4 # uint32 channel_map = audio_metadata["channel_map"] for channel_element in channel_map: new_box.channel_map.append(channel_element) new_box.content_size += 4 # uint32 return new_box def ambisonic_type_name(self): return next((key for key,value in SA3DBox.ambisonic_types.items() if value==self.ambisonic_type)) def ambisonic_channel_ordering_name(self): return next((key for key,value in SA3DBox.ambisonic_orderings.items() if value==self.ambisonic_channel_ordering)) def ambisonic_normalization_name(self): return next((key for key,value in SA3DBox.ambisonic_normalizations.items() if value==self.ambisonic_normalization)) def print_box(self, console): """ Prints the contents of this spatial audio (SA3D) box to the console. """ ambisonic_type = self.ambisonic_type_name() channel_ordering = self.ambisonic_channel_ordering_name() ambisonic_normalization = self.ambisonic_normalization_name() console("\t\tAmbisonic Type: %s" % ambisonic_type) console("\t\tContains Head-Locked Stereo: %r" % self.head_locked_stereo) console("\t\tAmbisonic Order: %d" % self.ambisonic_order) console("\t\tAmbisonic Channel Ordering: %s" % channel_ordering) console("\t\tAmbisonic Normalization: %s" % ambisonic_normalization) console("\t\tNumber of Channels: %d" % self.num_channels) console("\t\tChannel Map: %s" % str(self.channel_map)) def get_metadata_string(self): """ Outputs a concise single line audio metadata string. """ metadata = "%s, %s, %s, Order %d, %d Channel(s), Channel Map: %s" \ % (self.ambisonic_normalization_name(),\ self.ambisonic_channel_ordering_name(),\ self.ambisonic_type_name(),\ self.ambisonic_order,\ self.num_channels,\ str(self.channel_map)) return metadata def save(self, in_fh, out_fh, delta): if (self.header_size == 16): out_fh.write(struct.pack(">I", 1)) out_fh.write(struct.pack(">Q", self.size())) out_fh.write(self.name) elif(self.header_size == 8): out_fh.write(struct.pack(">I", self.size())) out_fh.write(self.name) ambisonic_type = ( self.ambisonic_type | int('10000000', 2) if self.head_locked_stereo else self.ambisonic_type & int('01111111', 2)) out_fh.write(struct.pack(">B", self.version)) out_fh.write(struct.pack(">B", ambisonic_type)) out_fh.write(struct.pack(">I", self.ambisonic_order)) out_fh.write(struct.pack(">B", self.ambisonic_channel_ordering)) out_fh.write(struct.pack(">B", self.ambisonic_normalization)) out_fh.write(struct.pack(">I", self.num_channels)) for i in self.channel_map: if (i != None): out_fh.write(struct.pack(">I", int(i)))