11defmodule ExWebRTC.Media.OggReader do
22 @ moduledoc """
3- Defines Ogg reader .
3+ Reads Opus packets from an Ogg container file .
44
55 For now, works only with single Opus stream in the container.
66
@@ -10,21 +10,8 @@ defmodule ExWebRTC.Media.OggReader do
1010 * [RFC 6716: Definition of the Opus Audio Codec](https://www.rfc-editor.org/rfc/rfc6716.txt)
1111 """
1212
13- import Bitwise
14-
15- @ crc_params % {
16- extend: :crc_32 ,
17- poly: 0x04C11DB7 ,
18- init: 0x0 ,
19- xorout: 0x0 ,
20- refin: false ,
21- refout: false
22- }
23-
24- @ signature "OggS"
25- @ id_signature "OpusHead"
26- @ comment_signature "OpusTags"
27- @ version 0
13+ alias ExWebRTC.Media.Ogg . { Header , Page }
14+ alias ExWebRTC.Media.Opus
2815
2916 @ opaque t ( ) :: % {
3017 file: File . io_device ( ) ,
@@ -38,17 +25,18 @@ defmodule ExWebRTC.Media.OggReader do
3825 For now, works only with single Opus stream in the container.
3926 This function reads the ID and Comment Headers (and, for now, ignores them).
4027 """
41- @ spec open ( Path . t ( ) ) :: { :ok , t ( ) } | { :error , File . posix ( ) | :invalid_header }
28+ @ spec open ( Path . t ( ) ) :: { :ok , t ( ) } | { :error , term ( ) }
4229 def open ( path ) do
4330 with { :ok , file } <- File . open ( path ) ,
4431 reader <- % { file: file , packets: [ ] , rest: << >> } ,
45- # for now, we ignore ID Header and Comment Header
46- { :ok , << @ id_signature , _rest :: binary >> , reader } <- do_next_packet ( reader ) ,
47- { :ok , << @ comment_signature , _rest :: binary >> , reader } <- do_next_packet ( reader ) do
32+ { :ok , id_header , reader } <- do_next_packet ( reader ) ,
33+ { :ok , comment_header , reader } <- do_next_packet ( reader ) ,
34+ :ok <- Header . decode_id ( id_header ) ,
35+ :ok <- Header . decode_comment ( comment_header ) do
4836 { :ok , reader }
4937 else
38+ :eof -> { :error , :invalid_file }
5039 { :error , _res } = err -> err
51- _other -> { :error , :invalid_header }
5240 end
5341 end
5442
@@ -59,13 +47,10 @@ defmodule ExWebRTC.Media.OggReader do
5947 This function also returns the duration of the audio in milliseconds, based on Opus packet TOC sequence (see RFC 6716, sec. 3).
6048 It assumes that all of the Ogg packets belong to the same stream.
6149 """
62- @ spec next_packet ( t ( ) ) ::
63- { :ok , { binary ( ) , non_neg_integer ( ) } , t ( ) }
64- | { :error , :invalid_page_header | :not_enough_data }
65- | :eof
50+ @ spec next_packet ( t ( ) ) :: { :ok , { binary ( ) , non_neg_integer ( ) } , t ( ) } | { :error , term ( ) } | :eof
6651 def next_packet ( reader ) do
6752 with { :ok , packet , reader } <- do_next_packet ( reader ) ,
68- { :ok , duration } <- get_packet_duration ( packet ) do
53+ { :ok , duration } <- Opus . duration ( packet ) do
6954 { :ok , { packet , duration } , reader }
7055 end
7156 end
@@ -75,7 +60,7 @@ defmodule ExWebRTC.Media.OggReader do
7560 end
7661
7762 defp do_next_packet ( % { packets: [ ] } = reader ) do
78- with { :ok , _header , packets , rest } <- read_page ( reader . file ) do
63+ with { :ok , % Page { packets: packets , rest: rest } } <- Page . read ( reader . file ) do
7964 case packets do
8065 [ ] ->
8166 do_next_packet ( % { reader | packets: [ ] , rest: reader . rest <> rest } )
@@ -87,82 +72,4 @@ defmodule ExWebRTC.Media.OggReader do
8772 end
8873 end
8974 end
90-
91- defp read_page ( file ) do
92- with << @ signature , @ version , type , granule_pos :: little - 64 , serial_no :: little - 32 ,
93- sequence_no :: little - 32 , _checksum :: little - 32 ,
94- segment_no >> = header <- IO . binread ( file , 27 ) ,
95- raw_segment_table when is_binary ( raw_segment_table ) <- IO . binread ( file , segment_no ) ,
96- segment_table <- :binary . bin_to_list ( raw_segment_table ) ,
97- payload_length <- Enum . sum ( segment_table ) ,
98- payload when is_binary ( payload ) <- IO . binread ( file , payload_length ) ,
99- :ok <- verify_checksum ( header <> raw_segment_table <> payload ) do
100- { packets , rest } = split_packets ( segment_table , payload )
101-
102- type = % {
103- fresh?: ( type &&& 0x01 ) != 0 ,
104- first?: ( type &&& 0x02 ) != 0 ,
105- last?: ( type &&& 0x04 ) != 0
106- }
107-
108- { :ok ,
109- % {
110- type: type ,
111- granule_pos: granule_pos ,
112- serial_no: serial_no ,
113- sequence_no: sequence_no
114- } , packets , rest }
115- else
116- data when is_binary ( data ) -> { :error , :invalid_page_header }
117- :eof -> :eof
118- { :error , _res } = err -> err
119- end
120- end
121-
122- defp verify_checksum ( << start :: binary - 22 , checksum :: little - 32 , rest :: binary >> ) do
123- actual_checksum =
124- << start :: binary , 0 :: 32 , rest :: binary >>
125- |> CRC . calculate ( @ crc_params )
126-
127- if checksum == actual_checksum do
128- :ok
129- else
130- { :error , :invalid_checksum }
131- end
132- end
133-
134- defp split_packets ( segment_table , payload , packets \\ [ ] , packet \\ << >> )
135- defp split_packets ( [ ] , << >> , packets , packet ) , do: { Enum . reverse ( packets ) , packet }
136-
137- defp split_packets ( [ segment_len | segment_table ] , payload , packets , packet ) do
138- << segment :: binary - size ( segment_len ) , rest :: binary >> = payload
139- packet = packet <> segment
140-
141- case segment_len do
142- 255 -> split_packets ( segment_table , rest , packets , packet )
143- _len -> split_packets ( segment_table , rest , [ packet | packets ] , << >> )
144- end
145- end
146-
147- # computes how much audio Opus packet contains (in ms), based on the TOC sequence
148- # RFC 6716, sec. 3
149- defp get_packet_duration ( << config :: 5 , rest :: bitstring >> ) do
150- with { :ok , frame_count } <- get_frame_count ( rest ) do
151- { :ok , trunc ( frame_count * get_frame_duration ( config ) ) }
152- end
153- end
154-
155- defp get_packet_duration ( _other ) , do: { :error , :not_enough_data }
156-
157- defp get_frame_count ( << _s :: 1 , 0 :: 2 , _rest :: binary >> ) , do: { :ok , 1 }
158- defp get_frame_count ( << _s :: 1 , c :: 2 , _rest :: binary >> ) when c in 1 .. 2 , do: { :ok , 2 }
159- defp get_frame_count ( << _s :: 1 , 3 :: 2 , _vp :: 2 , frame_no :: 5 , _rest :: binary >> ) , do: { :ok , frame_no }
160- defp get_frame_count ( _other ) , do: { :error , :not_enough_data }
161-
162- defp get_frame_duration ( config ) when config in [ 16 , 20 , 24 , 28 ] , do: 2.5
163- defp get_frame_duration ( config ) when config in [ 17 , 21 , 25 , 29 ] , do: 5
164- defp get_frame_duration ( config ) when config in [ 0 , 4 , 8 , 12 , 14 , 18 , 22 , 26 , 30 ] , do: 10
165- defp get_frame_duration ( config ) when config in [ 1 , 5 , 9 , 13 , 15 , 19 , 23 , 27 , 31 ] , do: 20
166- defp get_frame_duration ( config ) when config in [ 2 , 6 , 10 ] , do: 40
167- defp get_frame_duration ( config ) when config in [ 3 , 7 , 11 ] , do: 60
16875end
0 commit comments