Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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`, ownership is automatically 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
19 changes: 16 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,20 @@ 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()`

Creates a new IOContext and transfers ownership from this IOContext to the new instance. After transfer, this IOContext becomes inactive while the returned IOContext has full ownership. This enables safe ownership transfer between objects.

**Returns**: `IOContext` - A new IOContext instance with transferred ownership

**Example:**

```js
const sourceIO = new ffmpeg.IOContext(buffer)
const targetIO = sourceIO.transfer()
// 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. Ownership is automatically transferred to the format context, making the original IOContext safe to destroy multiple times.

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

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

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

Expand Down Expand Up @@ -86,7 +88,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 +127,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
16 changes: 15 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,8 +32,17 @@ module.exports = class FFmpegIOContext {
}

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

transfer() {
const to = new FFmpegIOContext(null, null)
to._handle = this._handle
this._handle = null
Comment thread
tony-go marked this conversation as resolved.
return to
}

[Symbol.dispose]() {
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
23 changes: 18 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,19 @@ 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 = sourceIO.transfer()

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