ender
HF Ready
a92043d
#! /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)))