├── .gitignore ├── .gitmodules ├── README.md ├── LICENSE.txt ├── gpmfstream ├── src │ ├── extractor.h │ ├── py_gpstream.cc │ └── extractor.cc └── __init__.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build/ 3 | cmake-build-*/ 4 | tmp/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gpmf-parser"] 2 | path = gpmf-parser 3 | url = https://github.com/gopro/gpmf-parser 4 | [submodule "deps/gpmf-parser"] 5 | path = deps/gpmf-parser 6 | url = https://github.com/gopro/gpmf-parser 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPMF Streams 2 | This package was created to extract sensor data 3 | from files containing GPMF telemetry. 4 | It has so far only been tested with the IMU data provided by GoPro Hero 5 and Hero 6 cameras. 5 | 6 | ## Example 7 | Extract all streams and then plot the gyroscope data 8 | 9 | streams = Stream.extract_streams(path) 10 | gyro = streams['GYRO'] 11 | plt.plot(gyro.timestamps, gyro.data) 12 | 13 | ## Timestamps 14 | Since the sensor does not provide per-sample timestamps, 15 | we must compute them explicitly. 16 | Currently, we assume a fixed sample rate, and compute it by 17 | simple linear interpolation. 18 | 19 | ## License 20 | This software is released under the MIT license. 21 | 22 | ## Dependencies 23 | The code depends on https://github.com/gopro/gpmf-parser. 24 | To install the correct version, run 'git submodule update --init' from within 25 | this git repo before compiling. 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hannes Ovrén 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /gpmfstream/src/extractor.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Hannes Ovrén 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef GOPRO_IMU_EXTRACT_EXTRACTOR_H 24 | #define GOPRO_IMU_EXTRACT_EXTRACTOR_H 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | struct Payload { 32 | size_t index; 33 | float start; 34 | float end; 35 | }; 36 | 37 | struct StreamData { 38 | double* buffer; 39 | size_t samples; 40 | size_t elements; 41 | size_t buffer_size; 42 | std::shared_ptr payload; 43 | }; 44 | 45 | struct Stream { 46 | Stream(const std::string key) : key(key) {}; 47 | std::string key; 48 | std::string name; 49 | std::vector> stream_data; 50 | std::vector units; 51 | }; 52 | 53 | struct GpmfExtractor { 54 | std::vector> payloads; 55 | std::map> streams; 56 | }; 57 | 58 | std::shared_ptr ExtractGpmf(const std::string& path); 59 | 60 | #endif //GOPRO_IMU_EXTRACT_EXTRACTOR_H 61 | -------------------------------------------------------------------------------- /gpmfstream/src/py_gpstream.cc: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Hannes Ovrén 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "extractor.h" 28 | 29 | namespace py = pybind11; 30 | 31 | void declare_stream(py::module& m) { 32 | using Class = Stream; 33 | auto cls = py::class_>(m, "Stream"); 34 | cls.def_readonly("key", &Class::key); 35 | cls.def_readonly("name", &Class::name); 36 | cls.def_readonly("stream_data", &Class::stream_data); 37 | cls.def_property_readonly("units", [](Class &self){ 38 | std::vector bytes_vector; 39 | 40 | for (auto& s : self.units) { 41 | bytes_vector.push_back(py::bytes(s)); 42 | } 43 | 44 | return bytes_vector; 45 | }); 46 | } 47 | 48 | void declare_streamdata(py::module& m) { 49 | using Class = StreamData; 50 | auto cls = py::class_>(m, "StreamData", py::buffer_protocol()); 51 | cls.def_readonly("samples", &Class::samples); 52 | cls.def_readonly("elements", &Class::samples); 53 | cls.def_readonly("payload", &Class::payload); 54 | cls.def_readonly("buffer", &Class::buffer); 55 | 56 | cls.def_buffer([](Class& obj) -> py::buffer_info { 57 | return py::buffer_info( 58 | obj.buffer, 59 | sizeof(double), 60 | py::format_descriptor::format(), 61 | 2, // dimesions 62 | {obj.samples, obj.elements}, // shape 63 | {sizeof(double) * obj.elements, // strides 64 | sizeof(double)} 65 | ); 66 | }); 67 | } 68 | 69 | void declare_payload(py::module& m) { 70 | using Class = Payload; 71 | auto cls = py::class_>(m, "Payload"); 72 | cls.def_readonly("index", &Class::index); 73 | cls.def_readonly("start", &Class::start); 74 | cls.def_readonly("end", &Class::end); 75 | 76 | } 77 | 78 | std::map> extract_streams(const std::string path) { 79 | // Throws exceptions on error 80 | auto extractor = ExtractGpmf(path); 81 | return extractor->streams; 82 | }; 83 | 84 | PYBIND11_MODULE(_gpmfstream, m) { 85 | declare_stream(m); 86 | declare_streamdata(m); 87 | declare_payload(m); 88 | 89 | m.def("extract_streams", &extract_streams); 90 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension, find_packages 2 | from setuptools.command.build_ext import build_ext 3 | import sys 4 | import setuptools 5 | 6 | __version__ = '0.5' 7 | 8 | 9 | class get_pybind_include(object): 10 | """Helper class to determine the pybind11 include path 11 | The purpose of this class is to postpone importing pybind11 12 | until it is actually installed, so that the ``get_include()`` 13 | method can be invoked. """ 14 | 15 | def __init__(self, user=False): 16 | self.user = user 17 | 18 | def __str__(self): 19 | import pybind11 20 | return pybind11.get_include(self.user) 21 | 22 | 23 | ext_modules = [ 24 | Extension( 25 | 'gpmfstream._gpmfstream', 26 | ['gpmfstream/src/py_gpstream.cc', 27 | 'gpmfstream/src/extractor.cc', 28 | 'deps/gpmf-parser/GPMF_parser.c', 29 | 'deps/gpmf-parser/demo/GPMF_mp4reader.c'], 30 | include_dirs=[ 31 | # Path to pybind11 headers 32 | get_pybind_include(), 33 | get_pybind_include(user=True), 34 | 35 | # GPMF 36 | 'deps/gpmf-parser/', 37 | 'deps/gpmf-parser/demo' 38 | ], 39 | language='c++' 40 | ), 41 | ] 42 | 43 | 44 | # As of Python 3.6, CCompiler has a `has_flag` method. 45 | # cf http://bugs.python.org/issue26689 46 | def has_flag(compiler, flagname): 47 | """Return a boolean indicating whether a flag name is supported on 48 | the specified compiler. 49 | """ 50 | import tempfile 51 | with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f: 52 | f.write('int main (int argc, char **argv) { return 0; }') 53 | try: 54 | compiler.compile([f.name], extra_postargs=[flagname]) 55 | except setuptools.distutils.errors.CompileError: 56 | return False 57 | return True 58 | 59 | 60 | def cpp_flag(compiler): 61 | """Return the -std=c++[11/14] compiler flag. 62 | The c++14 is prefered over c++11 (when it is available). 63 | """ 64 | if has_flag(compiler, '-std=c++14'): 65 | return '-std=c++14' 66 | elif has_flag(compiler, '-std=c++11'): 67 | return '-std=c++11' 68 | else: 69 | raise RuntimeError('Unsupported compiler -- at least C++11 support ' 70 | 'is needed!') 71 | 72 | 73 | class BuildExt(build_ext): 74 | """A custom build extension for adding compiler-specific options.""" 75 | c_opts = { 76 | 'msvc': ['/EHsc'], 77 | 'unix': [], 78 | } 79 | 80 | if sys.platform == 'darwin': 81 | c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] 82 | 83 | def build_extensions(self): 84 | ct = self.compiler.compiler_type 85 | opts = self.c_opts.get(ct, []) 86 | if ct == 'unix': 87 | opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version()) 88 | opts.append(cpp_flag(self.compiler)) 89 | if has_flag(self.compiler, '-fvisibility=hidden'): 90 | opts.append('-fvisibility=hidden') 91 | elif ct == 'msvc': 92 | opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version()) 93 | for ext in self.extensions: 94 | ext.extra_compile_args = opts 95 | build_ext.build_extensions(self) 96 | 97 | with open('README.md') as f: 98 | long_description = f.read() 99 | 100 | setup( 101 | name='gpmfstream', 102 | version=__version__, 103 | author='Hannes Ovrén', 104 | author_email='hannes.ovren@liu.se', 105 | url='https://github.com/hovren/gpmfstream', 106 | description='Extract GPMF metadata from GoPro MP4 videos', 107 | long_description=long_description, 108 | packages=find_packages(), 109 | ext_modules=ext_modules, 110 | install_requires=['pybind11>=2.2'], 111 | cmdclass={'build_ext': BuildExt}, 112 | zip_safe=False, 113 | ) -------------------------------------------------------------------------------- /gpmfstream/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2023 Hannes Ovrén 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | """GPMF stream extractor 24 | 25 | This module can be used to extract GPMF metadata from a video file. 26 | The GPMF data streams are usually sensor data (e.g. IMU or GPS) 27 | but can also be generated by other services on the device. 28 | 29 | Streams are extracted using the `Stream.extract_streams` method. 30 | 31 | Currently supported video formats are: MP4. 32 | """ 33 | 34 | from ._gpmfstream import extract_streams 35 | 36 | import numpy as np 37 | 38 | class Stream: 39 | """GPMF Stream 40 | 41 | Represents a GPMF stream loaded from e.g. a MP4 file. 42 | Handles extraction of timestamps. 43 | 44 | Example: 45 | streams = Stream.extract_streams(path) 46 | gyro = streams['GYRO'] 47 | plt.plot(gyro.timestamps, gyro.data) 48 | 49 | Extracting streams is not thread safe! 50 | """ 51 | 52 | def __init__(self, stream): 53 | """Create a new stream by wrapping a raw stream 54 | 55 | Use the extract_streams method to extract streams. 56 | """ 57 | self._stream = stream 58 | self._data = None 59 | self._timestamps = None 60 | self.rate = None 61 | self._units = None 62 | 63 | def __repr__(self): 64 | if self.name: 65 | return f"" 66 | else: 67 | return f"" 68 | 69 | @classmethod 70 | def extract_streams(cls, path): 71 | """Extract streams from a GPMF source file 72 | 73 | Returns a map of streams, by name/fourcc. 74 | """ 75 | return { 76 | fourcc: cls(stream) 77 | for fourcc, stream in extract_streams(str(path)).items() 78 | } 79 | 80 | @property 81 | def key(self): 82 | "Stream key (FOURCC)" 83 | return self._stream.key 84 | 85 | @property 86 | def name(self): 87 | "Stream name" 88 | return self._stream.name 89 | 90 | @property 91 | def units(self): 92 | """Sample units 93 | 94 | If all sample elements share the same unit, return a single string. 95 | Otherwise, return a tuple of strings, one per element. 96 | If units are missing for the stream, return None. 97 | """ 98 | if self._units is None: 99 | self._format_units() 100 | return self._units 101 | 102 | @property 103 | def data(self): 104 | "Data sample array of shape (nsamples, ndim)" 105 | if self._data is None: 106 | self._assemble_data() 107 | return self._data 108 | 109 | @property 110 | def timestamps(self): 111 | "Sample timestamps" 112 | if self._timestamps is None: 113 | self._compute_timestamps() 114 | return self._timestamps 115 | 116 | def _format_units(self): 117 | mapping = { 118 | b'\xb0': b'\xc2\xb0', # degree 119 | b'\xb2': b'\xc2\xb2', # square 120 | b'\xb3': b'\xc2\xb3', # cube 121 | b'\xb5': b'\xc2\xb5', # micro (mu) 122 | b'\x00\x00': b'', # zero 123 | } 124 | 125 | def cleanup(bs): 126 | for prev, new in mapping.items(): 127 | bs = bs.replace(prev, new) 128 | return bs.decode('utf8') 129 | 130 | unit_list = [cleanup(unit) for unit in self._stream.units] 131 | 132 | if not unit_list: 133 | self._units = None 134 | elif len(unit_list) > 1: 135 | self._units = tuple(unit_list) 136 | else: 137 | self._units = unit_list[0] 138 | 139 | def _assemble_data(self): 140 | self._data = np.vstack([sd for sd in self._stream.stream_data]) 141 | 142 | def _compute_timestamps(self): 143 | # Simple linear method 144 | payload_times = [(sd.samples, sd.payload.start, sd.payload.end) for sd in self._stream.stream_data] 145 | # Use second, and second to last to extract timing 146 | t0 = payload_times[1][1] 147 | t1 = payload_times[-2][2] 148 | nsamples = sum(n for n, *rest in payload_times[1:-1]) 149 | self.rate = nsamples / (t1 - t0) 150 | total_samples = sum(n for n, *rest in payload_times) 151 | first_payload_nsamples = payload_times[0][0] 152 | offset = t0 - first_payload_nsamples / self.rate 153 | self._timestamps = np.arange(total_samples) / self.rate + offset 154 | 155 | 156 | -------------------------------------------------------------------------------- /gpmfstream/src/extractor.cc: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Hannes Ovrén 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include "extractor.h" 26 | 27 | #include "GPMF_mp4reader.h" // <- This file keeps state! 28 | #include "GPMF_parser.h" 29 | 30 | std::string Key2String(uint32_t key) { 31 | char fourcc[4+1]; 32 | for (int i=0; i < 4; ++i) 33 | fourcc[i] = (key >> (i*8)) & 0xff; 34 | fourcc[4] = 0; 35 | return fourcc; 36 | } 37 | 38 | std::shared_ptr ExtractGpmf(const std::string& path) { 39 | int32_t ret = GPMF_OK; 40 | GPMF_stream metadata_stream, *ms = &metadata_stream; 41 | double metadatalength; 42 | uint32_t *payload = nullptr; //buffer to store GPMF samples from the MP4. 43 | 44 | metadatalength = OpenMP4Source(const_cast(path.c_str()), MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE); 45 | 46 | if (metadatalength <= 0.f) { 47 | throw std::invalid_argument("Failed to open GPMF source"); 48 | } 49 | 50 | uint32_t index, payloads; 51 | payloads = GetNumberPayloads(metadatalength); 52 | 53 | auto extractor = std::make_shared(); 54 | for (index = 0; index < payloads; index++) { 55 | uint32_t payloadsize = GetPayloadSize(metadatalength, index); 56 | payload = GetPayload(metadatalength, payload, index); 57 | 58 | if (payload == nullptr) 59 | throw std::runtime_error("Payload is null"); 60 | 61 | auto p = std::make_shared(); 62 | p->index = index; 63 | 64 | ret = GetPayloadTime(metadatalength, index, &p->start, &p->end); 65 | if (ret != GPMF_OK) 66 | throw std::runtime_error("Could not get payload times"); 67 | 68 | ret = GPMF_Init(ms, payload, payloadsize); 69 | if (ret != GPMF_OK) 70 | throw std::runtime_error("Could not initialize"); 71 | 72 | // Find all streams 73 | while (GPMF_OK == GPMF_FindNext(ms, GPMF_KEY_STREAM, GPMF_RECURSE_LEVELS)) { 74 | if (GPMF_OK == GPMF_SeekToSamples(ms)) { //find the last FOURCC within the stream 75 | uint32_t key = GPMF_Key(ms); 76 | auto sd = std::make_shared(); 77 | sd->payload = p; 78 | 79 | sd->elements = GPMF_ElementsInStruct(ms); 80 | sd->samples = GPMF_PayloadSampleCount(ms); 81 | 82 | auto key_string = Key2String(key); 83 | 84 | auto stream = extractor->streams[key_string]; 85 | if(stream == nullptr) { 86 | stream = std::make_shared(key_string); 87 | extractor->streams[key_string] = stream; 88 | } 89 | 90 | if (sd->samples) { 91 | sd->buffer_size = sd->samples * sd->elements * sizeof(double); 92 | sd->buffer = new double[sd->buffer_size]; 93 | 94 | GPMF_ScaledData(ms, sd->buffer, sd->buffer_size, 0, sd->samples, GPMF_TYPE_DOUBLE); 95 | stream->stream_data.push_back(sd); 96 | 97 | // Extract units? 98 | // FIXME: If no units found we should probably not continue looking 99 | if (stream->units.size() == 0) { 100 | //Search for any units to display 101 | GPMF_stream find_stream; 102 | GPMF_CopyState(ms, &find_stream); 103 | if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_SI_UNITS, GPMF_CURRENT_LEVEL) || 104 | GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_UNITS, GPMF_CURRENT_LEVEL)) 105 | { 106 | char *data = (char *)GPMF_RawData(&find_stream); 107 | int ssize = GPMF_StructSize(&find_stream); 108 | uint32_t unit_samples = GPMF_Repeat(&find_stream); 109 | 110 | for (int i = 0; i < unit_samples; i++) { 111 | std::string unit_string(data, ssize); 112 | stream->units.push_back(unit_string); 113 | data += ssize; 114 | } 115 | } 116 | } // extract units 117 | 118 | // Extract name? 119 | // FIXME: If no units found we should probably not continue looking 120 | if (stream->name.empty()) { 121 | //Search for any units to display 122 | GPMF_stream find_stream; 123 | GPMF_CopyState(ms, &find_stream); 124 | if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_STREAM_NAME, GPMF_CURRENT_LEVEL)) { 125 | char *data = (char *)GPMF_RawData(&find_stream); 126 | uint32_t nchars = GPMF_Repeat(&find_stream); 127 | std::string name_string(data, nchars); 128 | stream->name = name_string; 129 | } 130 | } // extract name 131 | 132 | } 133 | 134 | //extractor.streams[] 135 | } // SeekToSamples 136 | } // while next stream 137 | 138 | } 139 | 140 | return extractor; 141 | } 142 | --------------------------------------------------------------------------------