Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/input-format-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const format = new ffmpeg.InputFormatContext(io, options[, url])

### Parameters

- `io` (`IOContext` | `InputFormat`): The IO context or input format. The ownership of `io` is transferred.
- `io` (`IOContext` | `InputFormat`): The IO context or input format. When using `IOContext`, the native handle is transferred to the format context, making the original IOContext safe to destroy multiple times.
- `options` (`Dictionary`): Format options. Required when using `InputFormat`, ignored when using `IOContext`. The ownership of `options` is transferred.
- `url` (`string`, optional): Media source URL. Defaults to a platform-specific value

Expand Down
24 changes: 21 additions & 3 deletions docs/io-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ Callback function called when FFmpeg needs to seek within the data source.
const image = require('./fixtures/image/sample.jpeg', {
with: { type: 'binary' }
})
const io = new ffmpeg.IOContext(image)
io.destroy()
using io = new ffmpeg.IOContext(image)
```

### Streaming with custom read callback
Expand Down Expand Up @@ -105,6 +104,25 @@ const io = new ffmpeg.IOContext(4096, {

### `IOContext.destroy()`

Destroys the `IOContext` and frees all associated resources. Automatically called when the object is managed by a `using` declaration.
Destroys the `IOContext` and frees all associated resources. Automatically called when the object is managed by a `using` declaration. Safe to call multiple times - becomes a no-op after the native handle is transferred.

**Returns**: `void`

### `IOContext.transfer(targetIOContext)`

Transfers ownership from this IOContext to another IOContext. After transfer, this IOContext becomes inactive while the target takes full ownership. This enables safe ownership transfer between objects.

**Parameters:**

- `targetIOContext` (`IOContext`): The IOContext instance to receive ownership

**Returns**: `void`

**Example:**

```js
const sourceIO = new ffmpeg.IOContext(buffer)
const targetIO = new ffmpeg.IOContext(null, null) // Empty IOContext
sourceIO.transfer(targetIO)
// sourceIO is now safe to destroy, targetIO has full ownership
```
2 changes: 1 addition & 1 deletion docs/output-format-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const format = new ffmpeg.OutputFormatContext(formatName, io)
### Parameters

- `formatName` (`string`): The output format name (e.g., `'mp4'`, `'avi'`)
- `io` (`IOContext`): The IO context for writing. The ownership of `io` is transferred.
- `io` (`IOContext`): The IO context for writing. The native handle is transferred to the format context, making the original IOContext safe to destroy multiple times.

**Returns**: A new `OutputFormatContext` instance

Expand Down
10 changes: 7 additions & 3 deletions lib/format-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ const Dictionary = require('./dictionary')

class FFmpegFormatContext {
constructor(io) {
this._io = io
if (io) {
const ownedIO = new IOContext(null, null)
io.transfer(ownedIO)
this._io = ownedIO
Comment thread
tony-go marked this conversation as resolved.
Outdated
}
this._streams = []
}

Expand Down Expand Up @@ -86,7 +90,7 @@ exports.InputFormatContext = class FFmpegInputFormatContext extends FFmpegFormat
if (io instanceof IOContext) {
super(io)

this._handle = binding.openInputFormatContextWithIO(io._handle)
this._handle = binding.openInputFormatContextWithIO(this._io._handle)
} else if (io instanceof InputFormat) {
super()

Expand Down Expand Up @@ -125,7 +129,7 @@ exports.OutputFormatContext = class FFmpegOutputFormatContext extends FFmpegForm

if (typeof format === 'string') format = new OutputFormat(format)

this._handle = binding.openOutputFormatContext(format._handle, io._handle)
this._handle = binding.openOutputFormatContext(format._handle, this._io._handle)
this._isOutput = true
}

Expand Down
14 changes: 13 additions & 1 deletion lib/io-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ const binding = require('../binding')

module.exports = class FFmpegIOContext {
constructor(buffer, opts = {}) {
if (buffer === null && opts === null) {
this._handle = null
return
}

let offset = 0
let len = 0

Expand All @@ -27,7 +32,14 @@ module.exports = class FFmpegIOContext {
}

destroy() {
binding.destroyIOContext(this._handle)
if (this._handle) {
binding.destroyIOContext(this._handle)
this._handle = null
}
}

transfer(to) {
Comment thread
tony-go marked this conversation as resolved.
Outdated
to._handle = this._handle
this._handle = null
Comment thread
tony-go marked this conversation as resolved.
}

Expand Down
6 changes: 3 additions & 3 deletions test/decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ test('decode .webm', (t) => {
})

function decodeImage(image) {
const io = new ffmpeg.IOContext(image)
using io = new ffmpeg.IOContext(image)
using format = new ffmpeg.InputFormatContext(io)

let result
Expand Down Expand Up @@ -123,7 +123,7 @@ function decodeImage(image) {
}

function decodeAudio(audio) {
const io = new ffmpeg.IOContext(audio)
using io = new ffmpeg.IOContext(audio)
using format = new ffmpeg.InputFormatContext(io)

let result
Expand Down Expand Up @@ -189,7 +189,7 @@ function decodeAudio(audio) {
}

function decodeVideo(video) {
const io = new ffmpeg.IOContext(video)
using io = new ffmpeg.IOContext(video)
using format = new ffmpeg.InputFormatContext(io)

using packet = new ffmpeg.Packet()
Expand Down
7 changes: 3 additions & 4 deletions test/format-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ test('InputFormatContext should be instantiate with IOContext', (t) => {
const image = require('./fixtures/image/sample.jpeg', {
with: { type: 'binary' }
})
const io = new ffmpeg.IOContext(image)

using io = new ffmpeg.IOContext(image)
using inputFormatContext = new ffmpeg.InputFormatContext(io)

t.ok(inputFormatContext)
Expand Down Expand Up @@ -76,8 +75,8 @@ test('InputFormatContext.inputFormat should expose a inputFormat getter', (t) =>
// OutputFormatContext

test('OutputFormatContext should expose an outputFormat getter', (t) => {
const io = new ffmpeg.IOContext(4096)
const outContext = new ffmpeg.OutputFormatContext(new ffmpeg.OutputFormat('webm'), io)
using io = new ffmpeg.IOContext(4096)
using outContext = new ffmpeg.OutputFormatContext(new ffmpeg.OutputFormat('webm'), io)

const outputFormat = outContext.outputFormat

Expand Down
24 changes: 19 additions & 5 deletions test/io-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { mediaTypes } = ffmpeg.constants

test('IOContext should propagate onread throwed error properly', (t) => {
const readError = 'read error'
const io = new ffmpeg.IOContext(4096, {
using io = new ffmpeg.IOContext(4096, {
onread: () => {
throw new Error(readError)
}
Expand All @@ -26,7 +26,7 @@ test('IOContext should propagate onseek throwed error properly', (t) => {
const seekError = 'seek error'

let offset = 0
const io = new ffmpeg.IOContext(4096, {
using io = new ffmpeg.IOContext(4096, {
onread: (buffer) => {
const remaining = data.length - offset
if (remaining <= 0) return 0
Expand All @@ -51,7 +51,7 @@ test('IOContext should propagate onseek throwed error properly', (t) => {

test('IOContext should propagate onwrite throwed error properly', (t) => {
const writeError = 'write error'
const io = new ffmpeg.IOContext(4096, {
using io = new ffmpeg.IOContext(4096, {
onwrite: () => {
throw new Error(writeError)
}
Expand Down Expand Up @@ -80,7 +80,7 @@ test('IOContext streaming webm with onread', (t) => {
})

let offset = 0
const io = new ffmpeg.IOContext(4096, {
using io = new ffmpeg.IOContext(4096, {
onread: (buffer) => {
if (!offset) {
t.ok(Buffer.isBuffer(buffer), 'is buffer')
Expand Down Expand Up @@ -112,7 +112,7 @@ test('IOContext streaming mp4 with onseek', (t) => {
})

let offset = 0
const io = new ffmpeg.IOContext(4096, {
using io = new ffmpeg.IOContext(4096, {
onread: (buffer) => {
if (!offset) {
t.ok(Buffer.isBuffer(buffer), 'is buffer')
Expand Down Expand Up @@ -153,6 +153,20 @@ test('IOContext streaming mp4 with onseek', (t) => {
t.is(audio.length, 34914, `audio size: got ${audio.length}, expected 34914`)
})

test('IOContext.transfer() should transfer ownership between IOContext instances', (t) => {
const buffer = require('./fixtures/image/sample.jpeg', {
with: { type: 'binary' }
})
using sourceIO = new ffmpeg.IOContext(buffer)
using targetIO = new ffmpeg.IOContext(null, null)

sourceIO.transfer(targetIO)

using format = new ffmpeg.InputFormatContext(targetIO)
t.ok(format, 'targetIO works after transfer')
t.pass('both IOContext instances can be destroyed safely')
})

// Helpers

function runStreams(io) {
Expand Down
4 changes: 2 additions & 2 deletions test/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ function fillPacket(packet) {
const image = require('./fixtures/image/sample.jpeg', {
with: { type: 'binary' }
})
const io = new ffmpeg.IOContext(image)
const format = new ffmpeg.InputFormatContext(io)
using io = new ffmpeg.IOContext(image)
using format = new ffmpeg.InputFormatContext(io)
format.readFrame(packet)
}

Expand Down
8 changes: 3 additions & 5 deletions test/resampler.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ test('resampler with audio from aiff file', (t) => {
const audio = require('./fixtures/audio/sample.aiff', {
with: { type: 'binary' }
})
const io = new ffmpeg.IOContext(audio)
const format = new ffmpeg.InputFormatContext(io)
using io = new ffmpeg.IOContext(audio)
using format = new ffmpeg.InputFormatContext(io)

for (const stream of format.streams) {
const decoder = stream.decoder()
Expand Down Expand Up @@ -146,16 +146,14 @@ test('resampler with audio from aiff file', (t) => {
decoder.destroy()
resampler.destroy()
}

format.destroy()
})

test('resampler converts between different sample formats', (t) => {
const audio = require('./fixtures/audio/sample.aiff', {
with: { type: 'binary' }
})

const io = new ffmpeg.IOContext(audio)
using io = new ffmpeg.IOContext(audio)
using format = new ffmpeg.InputFormatContext(io)
const stream = format.streams[0]
using decoder = stream.decoder()
Expand Down
18 changes: 8 additions & 10 deletions test/scaler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,36 @@ test('it should preseve line number in case of downscale', (t) => {
with: { type: 'binary' }
})

const io = new ffmpeg.IOContext(image)
using io = new ffmpeg.IOContext(image)
using format = new ffmpeg.InputFormatContext(io)

for (const stream of format.streams) {
using packet = new ffmpeg.Packet()
format.readFrame(packet)

using raw = new ffmpeg.Frame()
using rgba = new ffmpeg.Frame()

using decoder = stream.decoder()
decoder.open()
decoder.sendPacket(packet)
decoder.receiveFrame(raw)

const targetWidth = decoder.width / 2
const targetHeight = decoder.height / 2

rgba.width = targetWidth
rgba.height = targetHeight
rgba.pixelFormat = ffmpeg.constants.pixelFormats.RGBA
using rgba = new ffmpeg.Frame()
rgba.width = decoder.width / 2
rgba.height = decoder.height / 2
rgba.format = ffmpeg.constants.pixelFormats.RGBA
rgba.alloc()

using scaler = new ffmpeg.Scaler(
decoder.pixelFormat,
decoder.width,
decoder.height,
rgba.pixelFormat,
rgba.format,
rgba.width,
rgba.height
)
const lines = scaler.scale(raw, rgba)

t.ok(lines == targetHeight)
t.ok(lines == rgba.height)
}
})
Loading