@lumen5/beamcoder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +41 -0
- package/.circleci/images/testbeam10-4.1/Dockerfile +12 -0
- package/.circleci/test_image/Dockerfile +14 -0
- package/.circleci/test_image/build.md +13 -0
- package/.eslintrc.js +27 -0
- package/.github/workflows/publish-npm.yml +33 -0
- package/LICENSE +674 -0
- package/README.md +1221 -0
- package/beamstreams.js +692 -0
- package/binding.gyp +103 -0
- package/examples/encode_h264.js +92 -0
- package/examples/jpeg_app.js +55 -0
- package/examples/jpeg_filter_app.js +101 -0
- package/examples/make_mp4.js +123 -0
- package/images/beamcoder_small.jpg +0 -0
- package/index.d.ts +83 -0
- package/index.js +44 -0
- package/install_ffmpeg.js +240 -0
- package/package.json +45 -0
- package/scratch/decode_aac.js +38 -0
- package/scratch/decode_avci.js +50 -0
- package/scratch/decode_hevc.js +38 -0
- package/scratch/decode_pcm.js +39 -0
- package/scratch/make_a_mux.js +68 -0
- package/scratch/muxer.js +74 -0
- package/scratch/read_wav.js +35 -0
- package/scratch/simple_mux.js +39 -0
- package/scratch/stream_avci.js +127 -0
- package/scratch/stream_mp4.js +78 -0
- package/scratch/stream_mux.js +47 -0
- package/scratch/stream_pcm.js +82 -0
- package/scratch/stream_wav.js +62 -0
- package/scripts/install_beamcoder_dependencies.sh +25 -0
- package/src/adaptor.h +202 -0
- package/src/beamcoder.cc +937 -0
- package/src/beamcoder_util.cc +1129 -0
- package/src/beamcoder_util.h +206 -0
- package/src/codec.cc +7386 -0
- package/src/codec.h +44 -0
- package/src/codec_par.cc +1818 -0
- package/src/codec_par.h +40 -0
- package/src/decode.cc +569 -0
- package/src/decode.h +75 -0
- package/src/demux.cc +584 -0
- package/src/demux.h +88 -0
- package/src/encode.cc +496 -0
- package/src/encode.h +72 -0
- package/src/filter.cc +1888 -0
- package/src/filter.h +30 -0
- package/src/format.cc +5287 -0
- package/src/format.h +77 -0
- package/src/frame.cc +2681 -0
- package/src/frame.h +52 -0
- package/src/governor.cc +286 -0
- package/src/governor.h +30 -0
- package/src/hwcontext.cc +378 -0
- package/src/hwcontext.h +35 -0
- package/src/log.cc +186 -0
- package/src/log.h +20 -0
- package/src/mux.cc +834 -0
- package/src/mux.h +106 -0
- package/src/packet.cc +762 -0
- package/src/packet.h +49 -0
- package/test/codecParamsSpec.js +148 -0
- package/test/decoderSpec.js +56 -0
- package/test/demuxerSpec.js +41 -0
- package/test/encoderSpec.js +69 -0
- package/test/filtererSpec.js +47 -0
- package/test/formatSpec.js +343 -0
- package/test/frameSpec.js +145 -0
- package/test/introspectionSpec.js +73 -0
- package/test/muxerSpec.js +34 -0
- package/test/packetSpec.js +122 -0
- package/types/Beamstreams.d.ts +98 -0
- package/types/Codec.d.ts +123 -0
- package/types/CodecContext.d.ts +555 -0
- package/types/CodecPar.d.ts +108 -0
- package/types/Decoder.d.ts +137 -0
- package/types/Demuxer.d.ts +113 -0
- package/types/Encoder.d.ts +94 -0
- package/types/Filter.d.ts +324 -0
- package/types/FormatContext.d.ts +380 -0
- package/types/Frame.d.ts +295 -0
- package/types/HWContext.d.ts +62 -0
- package/types/Muxer.d.ts +121 -0
- package/types/Packet.d.ts +82 -0
- package/types/PrivClass.d.ts +25 -0
- package/types/Stream.d.ts +165 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Aerostat Beam Coder - Node.js native bindings for FFmpeg.
|
|
3
|
+
Copyright (C) 2019 Streampunk Media Ltd.
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
https://www.streampunk.media/ mailto:furnace@streampunk.media
|
|
19
|
+
14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const test = require('tape');
|
|
23
|
+
const beamcoder = require('../index.js');
|
|
24
|
+
|
|
25
|
+
test('Create a packet', t => {
|
|
26
|
+
let pkt = beamcoder.packet();
|
|
27
|
+
t.ok(pkt, 'is truthy.');
|
|
28
|
+
t.equal(typeof pkt._packet, 'object', 'external value present.');
|
|
29
|
+
t.deepEqual(pkt, { type: 'Packet',
|
|
30
|
+
pts: null,
|
|
31
|
+
dts: null,
|
|
32
|
+
data: null,
|
|
33
|
+
size: 0,
|
|
34
|
+
stream_index: 0,
|
|
35
|
+
flags:
|
|
36
|
+
{ KEY: false,
|
|
37
|
+
CORRUPT: false,
|
|
38
|
+
DISCARD: false,
|
|
39
|
+
TRUSTED: false,
|
|
40
|
+
DISPOSABLE: false },
|
|
41
|
+
side_data: null,
|
|
42
|
+
duration: 0,
|
|
43
|
+
pos: -1 }, 'has expected minimal value.');
|
|
44
|
+
t.end();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Minimal JSON serialization', t => {
|
|
48
|
+
let pkt = beamcoder.packet();
|
|
49
|
+
t.equal(typeof pkt.toJSON, 'function', 'has hidden toJSON function.');
|
|
50
|
+
let ps = JSON.stringify(pkt);
|
|
51
|
+
t.equal(typeof ps, 'string', 'stringify created a string.');
|
|
52
|
+
let pps = JSON.parse(ps);
|
|
53
|
+
t.deepEqual(pps, { type: 'Packet', stream_index: 0 }, 'made minimal value.');
|
|
54
|
+
let rpkt = beamcoder.packet(ps);
|
|
55
|
+
t.ok(rpkt, 'roundtrip packet is truthy.');
|
|
56
|
+
t.deepEqual(rpkt, { type: 'Packet',
|
|
57
|
+
pts: null,
|
|
58
|
+
dts: null,
|
|
59
|
+
data: null,
|
|
60
|
+
size: 0,
|
|
61
|
+
stream_index: 0,
|
|
62
|
+
flags:
|
|
63
|
+
{ KEY: false,
|
|
64
|
+
CORRUPT: false,
|
|
65
|
+
DISCARD: false,
|
|
66
|
+
TRUSTED: false,
|
|
67
|
+
DISPOSABLE: false },
|
|
68
|
+
side_data: null,
|
|
69
|
+
duration: 0,
|
|
70
|
+
pos: -1 }, 'has expected minimal value.');
|
|
71
|
+
t.end();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('Maximal JSON serialization', t => {
|
|
75
|
+
let pkt = beamcoder.packet({ type: 'Packet',
|
|
76
|
+
pts: 42,
|
|
77
|
+
dts: 43,
|
|
78
|
+
data: Buffer.from('wibble'),
|
|
79
|
+
stream_index: 7,
|
|
80
|
+
flags:
|
|
81
|
+
{ KEY: true,
|
|
82
|
+
CORRUPT: false,
|
|
83
|
+
DISCARD: false,
|
|
84
|
+
TRUSTED: true,
|
|
85
|
+
DISPOSABLE: false },
|
|
86
|
+
side_data: { replaygain: Buffer.from('wobble') },
|
|
87
|
+
duration: 44,
|
|
88
|
+
pos: 45 });
|
|
89
|
+
let ps = JSON.stringify(pkt);
|
|
90
|
+
t.equal(typeof ps, 'string', 'stringify created a string.');
|
|
91
|
+
let rpkt = beamcoder.packet(ps);
|
|
92
|
+
t.ok(rpkt, 'roundtrip packet is truthy.');
|
|
93
|
+
t.deepEqual(rpkt, { type: 'Packet',
|
|
94
|
+
pts: 42,
|
|
95
|
+
dts: 43,
|
|
96
|
+
data: null,
|
|
97
|
+
size: 0,
|
|
98
|
+
stream_index: 7,
|
|
99
|
+
flags:
|
|
100
|
+
{ KEY: true,
|
|
101
|
+
CORRUPT: false,
|
|
102
|
+
DISCARD: false,
|
|
103
|
+
TRUSTED: true,
|
|
104
|
+
DISPOSABLE: false },
|
|
105
|
+
side_data:
|
|
106
|
+
{ type: 'PacketSideData',
|
|
107
|
+
replaygain: Buffer.from([0x77, 0x6f, 0x62, 0x62, 0x6c, 0x65]) },
|
|
108
|
+
duration: 44,
|
|
109
|
+
pos: 45 }, 'roundtrips expected values.');
|
|
110
|
+
t.end();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Reset packet data', t => {
|
|
114
|
+
let p = beamcoder.packet({ pts: 42, size: 4321, data: Buffer.alloc(4321 + beamcoder.AV_INPUT_BUFFER_PADDING_SIZE) });
|
|
115
|
+
t.ok(Buffer.isBuffer(p.data), 'data is a buffer.');
|
|
116
|
+
t.notEqual(p.data.length, p.size, 'data length is greater than the packet size.');
|
|
117
|
+
t.equal(p.data.length, 4321 + beamcoder.AV_INPUT_BUFFER_PADDING_SIZE, 'length is as expected.');
|
|
118
|
+
p.data = null;
|
|
119
|
+
t.equal(p.data, null, 'after reset, packet data is set to null.');
|
|
120
|
+
t.equal(p.size, 4321, 'after reset, size remains at original value.');
|
|
121
|
+
t.end();
|
|
122
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Demuxer, DemuxerCreateOptions } from "./Demuxer"
|
|
2
|
+
import { Muxer, MuxerCreateOptions } from "./Muxer"
|
|
3
|
+
import { InputFormat } from "./FormatContext"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A [Node.js Writable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_streams)
|
|
7
|
+
* allowing source data to be streamed to the demuxer from a file or other stream source such as a network connection
|
|
8
|
+
*/
|
|
9
|
+
export interface WritableDemuxerStream extends NodeJS.WritableStream {
|
|
10
|
+
/**
|
|
11
|
+
* Create a demuxer for this source
|
|
12
|
+
* @param options a DemuxerCreateOptions object
|
|
13
|
+
* @returns a promise that resolves to a Demuxer when it has determined sufficient
|
|
14
|
+
* format details by consuming data from the source. The promise will wait indefinitely
|
|
15
|
+
* until sufficient source data has been read.
|
|
16
|
+
*/
|
|
17
|
+
demuxer(options: DemuxerCreateOptions): Promise<Demuxer>
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a WritableDemuxerStream to allow streaming to a Demuxer
|
|
21
|
+
* @param options.highwaterMark Buffer level when `stream.write()` starts returng false.
|
|
22
|
+
* @returns A WritableDemuxerStream that can be streamed to.
|
|
23
|
+
*/
|
|
24
|
+
export function demuxerStream(options: { highwaterMark?: number }): WritableDemuxerStream
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A [Node.js Readable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_streams)
|
|
28
|
+
* allowing data to be streamed from the muxer to a file or other stream destination such as a network connection
|
|
29
|
+
*/
|
|
30
|
+
export interface ReadableMuxerStream extends NodeJS.ReadableStream {
|
|
31
|
+
/**
|
|
32
|
+
* Create a muxer for this source
|
|
33
|
+
* @param options a MuxerCreateOptions object
|
|
34
|
+
* @returns A Muxer object
|
|
35
|
+
*/
|
|
36
|
+
muxer(options: MuxerCreateOptions): Muxer
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a ReadableMuxerStream to allow streaming from a Muxer
|
|
40
|
+
* @param options.highwaterMark The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource.
|
|
41
|
+
* @returns A ReadableMuxerStream that can be streamed from.
|
|
42
|
+
*/
|
|
43
|
+
export function muxerStream(options: { highwaterMark?: number }): ReadableMuxerStream
|
|
44
|
+
|
|
45
|
+
/** Create object for AVIOContext based buffered I/O */
|
|
46
|
+
export function governor(options: { highWaterMark: number }): {
|
|
47
|
+
read(len: number): Promise<Buffer>
|
|
48
|
+
write(data: Buffer): Promise<null>
|
|
49
|
+
finish(): undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Source definition for a beamstream channel, from either a file or NodeJS ReadableStream */
|
|
53
|
+
export interface BeamstreamSource {
|
|
54
|
+
url?: string
|
|
55
|
+
input_stream?: NodeJS.ReadableStream
|
|
56
|
+
ms?: { start: number, end: number }
|
|
57
|
+
streamIndex?: number
|
|
58
|
+
iformat?: InputFormat
|
|
59
|
+
options?: { [key: string]: any }
|
|
60
|
+
}
|
|
61
|
+
/** Codec definition for the destination channel */
|
|
62
|
+
export interface BeamstreamStream {
|
|
63
|
+
name: string
|
|
64
|
+
time_base: Array<number>
|
|
65
|
+
codecpar: { [key: string]: any }
|
|
66
|
+
}
|
|
67
|
+
/** Definition for a channel of beamstream processing */
|
|
68
|
+
export interface BeamstreamChannel {
|
|
69
|
+
sources: Array<BeamstreamSource>
|
|
70
|
+
filterSpec: string
|
|
71
|
+
streams: Array<BeamstreamStream>
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Definition for a beamstream process consisting of a number of audio and video sources
|
|
75
|
+
* that are to be processed and multiplexed into an output file or stream
|
|
76
|
+
*/
|
|
77
|
+
export interface BeamstreamParams {
|
|
78
|
+
video?: Array<BeamstreamChannel>
|
|
79
|
+
audio?: Array<BeamstreamChannel>
|
|
80
|
+
/** Destination definition for the beamstream process, to either a file or NodeJS WritableStream */
|
|
81
|
+
out: {
|
|
82
|
+
formatName: string
|
|
83
|
+
url?: string
|
|
84
|
+
output_stream?: NodeJS.WritableStream
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Initialise the sources for the beamstream process.
|
|
89
|
+
* Note - the params object is updated by the function.
|
|
90
|
+
*/
|
|
91
|
+
export function makeSources(params: BeamstreamParams): Promise<null>
|
|
92
|
+
/**
|
|
93
|
+
* Initialise the output streams for the beamstream process.
|
|
94
|
+
* Note - the params object is updated by the function.
|
|
95
|
+
* @returns Promise which resolves to an object with a run function that starts the processing
|
|
96
|
+
*/
|
|
97
|
+
export function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise<null>} >
|
|
98
|
+
|
package/types/Codec.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { PrivClass } from "./PrivClass"
|
|
2
|
+
|
|
3
|
+
export interface Codec {
|
|
4
|
+
/** Object name. */
|
|
5
|
+
readonly type: 'Codec'
|
|
6
|
+
/**
|
|
7
|
+
* Name of the codec implementation.
|
|
8
|
+
* The name is globally unique among encoders and among decoders (but an
|
|
9
|
+
* encoder and a decoder can share the same name).
|
|
10
|
+
* This is the primary way to find a codec from the user perspective.
|
|
11
|
+
*/
|
|
12
|
+
readonly name: string
|
|
13
|
+
/** Descriptive name for the codec, meant to be more human readable than name. */
|
|
14
|
+
readonly long_name: string
|
|
15
|
+
/** String describing the media type */
|
|
16
|
+
readonly codec_type: 'unknown' | 'video' | 'audio' | 'data' | 'subtitle' | 'attachment' | 'nb'
|
|
17
|
+
/** Number that identifies the syntax and semantics of the bitstream. */
|
|
18
|
+
readonly id: number
|
|
19
|
+
/** true if codec is an decoder */
|
|
20
|
+
readonly decoder: boolean
|
|
21
|
+
/** true if codec is an encoder */
|
|
22
|
+
readonly encoder: boolean
|
|
23
|
+
/** Codec capabilities - see AV_CODEC_CAP_* */
|
|
24
|
+
readonly capabilities: {
|
|
25
|
+
/** Decoder can use draw_horiz_band callback. */
|
|
26
|
+
DRAW_HORIZ_BAND: boolean
|
|
27
|
+
/** Codec uses get_buffer() for allocating buffers and supports custom allocators. */
|
|
28
|
+
DR1: boolean
|
|
29
|
+
TRUNCATED: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Decoder requires flushing with NULL input at the end in order to
|
|
32
|
+
* give the complete and correct output.
|
|
33
|
+
|
|
34
|
+
* NOTE: If this flag is not set, the codec is guaranteed to never be fed with
|
|
35
|
+
* with NULL data. The user can still send NULL data to the decode function,
|
|
36
|
+
* but it will not be passed along to the codec unless this flag is set.
|
|
37
|
+
*
|
|
38
|
+
* The decoder has a non-zero delay and needs to be fed with avpkt->data=NULL,
|
|
39
|
+
* avpkt->size=0 at the end to get the delayed data until the decoder no longer
|
|
40
|
+
* returns frames.
|
|
41
|
+
*/
|
|
42
|
+
DELAY: boolean
|
|
43
|
+
/** Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples. */
|
|
44
|
+
SMALL_LAST_FRAME: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Codec can output multiple frames per APacket
|
|
47
|
+
* Normally demuxers return one frame at a time, demuxers which do not do
|
|
48
|
+
* are connected to a parser to split what they return into proper frames.
|
|
49
|
+
* This flag is reserved to the very rare category of codecs which have a
|
|
50
|
+
* bitstream that cannot be split into frames without timeconsuming
|
|
51
|
+
* operations like full decoding. Demuxers carrying such bitstreams thus
|
|
52
|
+
* may return multiple frames in a packet. This has many disadvantages like
|
|
53
|
+
* prohibiting stream copy in many cases thus it should only be considered
|
|
54
|
+
* as a last resort.
|
|
55
|
+
*/
|
|
56
|
+
SUBFRAMES: boolean
|
|
57
|
+
/** Codec is experimental and is thus avoided in favor of non experimental codecs */
|
|
58
|
+
EXPERIMENTAL: boolean
|
|
59
|
+
/** Codec should fill in channel configuration and samplerate instead of container */
|
|
60
|
+
CHANNEL_CONF: boolean
|
|
61
|
+
/** Codec supports frame-level multithreading. */
|
|
62
|
+
FRAME_THREADS: boolean
|
|
63
|
+
/** Codec supports slice-based (or partition-based) multithreading. */
|
|
64
|
+
SLICE_THREADS: boolean
|
|
65
|
+
/** Codec supports changed parameters at any point. */
|
|
66
|
+
PARAM_CHANGE: boolean
|
|
67
|
+
/** Codec supports avctx->thread_count == 0 (auto). */
|
|
68
|
+
AUTO_THREADS: boolean
|
|
69
|
+
/** Audio encoder supports receiving a different number of samples in each call. */
|
|
70
|
+
VARIABLE_FRAME_SIZE: boolean
|
|
71
|
+
/**
|
|
72
|
+
* Decoder is not a preferred choice for probing.
|
|
73
|
+
* This indicates that the decoder is not a good choice for probing.
|
|
74
|
+
* It could for example be an expensive to spin up hardware decoder,
|
|
75
|
+
* or it could simply not provide a lot of useful information about
|
|
76
|
+
* the stream.
|
|
77
|
+
* A decoder marked with this flag should only be used as last resort
|
|
78
|
+
* choice for probing.
|
|
79
|
+
*/
|
|
80
|
+
AVOID_PROBING: boolean
|
|
81
|
+
/** Codec is intra only. */
|
|
82
|
+
INTRA_ONLY: boolean
|
|
83
|
+
/** Codec is lossless. */
|
|
84
|
+
LOSSLESS: boolean
|
|
85
|
+
/** Codec is backed by a hardware implementation. Typically used to identify a non-hwaccel hardware decoder. */
|
|
86
|
+
HARDWARE: boolean
|
|
87
|
+
/**
|
|
88
|
+
* Codec is potentially backed by a hardware implementation, but not necessarily.
|
|
89
|
+
* This is used instead of the HARDWARE flag if the implementation provides some sort of internal fallback.
|
|
90
|
+
*/
|
|
91
|
+
HYBRID: boolean
|
|
92
|
+
}
|
|
93
|
+
/** Array of supported framerates (as a rational [num, den]), or null if unknown. */
|
|
94
|
+
readonly supported_framerates: ReadonlyArray<ReadonlyArray<number>> | null
|
|
95
|
+
/** Array of supported pixel formats, or null if unknown. */
|
|
96
|
+
readonly pix_fmts: ReadonlyArray<string> | null
|
|
97
|
+
/** Array of supported audio samplerates, or null if unknown */
|
|
98
|
+
readonly supported_samplerates: ReadonlyArray<number> | null
|
|
99
|
+
/** Array of supported sample formats, or NULL if unknown, */
|
|
100
|
+
readonly sample_fmts: ReadonlyArray<string>
|
|
101
|
+
/** */
|
|
102
|
+
readonly channel_layouts: ReadonlyArray<string>
|
|
103
|
+
/** */
|
|
104
|
+
readonly max_lowres: number
|
|
105
|
+
/** Class for private context */
|
|
106
|
+
readonly priv_class: PrivClass
|
|
107
|
+
/** */
|
|
108
|
+
readonly profiles: ReadonlyArray<string> | null
|
|
109
|
+
/** */
|
|
110
|
+
readonly wrapper_name?: string
|
|
111
|
+
/** */
|
|
112
|
+
readonly descriptor: {
|
|
113
|
+
INTRA_ONLY: boolean
|
|
114
|
+
LOSSY: boolean
|
|
115
|
+
LOSSLESS: boolean
|
|
116
|
+
REORDER: boolean
|
|
117
|
+
BITMAP_SUB: boolean
|
|
118
|
+
TEXT_SUB: boolean
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** List the available codecs */
|
|
123
|
+
export function codecs(): { [key: string]: { encoder?: Codec, decoder?: Codec }}
|