Skip to content

Commit

Permalink
feat: support UploadBufferInfo in uploadStill and uploadClip api
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Jan 24, 2024
1 parent 672e871 commit 9e91923
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 37 deletions.
31 changes: 15 additions & 16 deletions src/atem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import PLazy = require('p-lazy')
import { TimeCommand } from './commands'
import { TimeInfo } from './state/info'
import { SomeAtemAudioLevels } from './state/levels'
import { generateUploadBufferInfo, UploadBufferInfo } from './dataTransfer/dataTransferUploadBuffer'

export interface AtemOptions {
address?: string
Expand Down Expand Up @@ -750,38 +751,36 @@ export class Atem extends BasicAtem {

public async uploadStill(
index: number,
data: Buffer,
data: Buffer | UploadBufferInfo,
name: string,
description: string,
options?: DT.UploadStillEncodingOptions
): Promise<void> {
if (!this.state) return Promise.reject()
if (!this.state) throw new Error('Unable to check current resolution')
const resolution = Util.getVideoModeInfo(this.state.settings.videoMode)
if (!resolution) return Promise.reject()
return this.dataTransferManager.uploadStill(
index,
Util.convertRGBAToYUV422(resolution.width, resolution.height, data),
name,
description,
options
)
if (!resolution) throw new Error('Failed to determine required resolution')

const encodedData = generateUploadBufferInfo(data, resolution, !options?.disableRLE)

return this.dataTransferManager.uploadStill(index, encodedData, name, description)
}

public async uploadClip(
index: number,
frames: Iterable<Buffer> | AsyncIterable<Buffer>,
frames: Iterable<Buffer> | AsyncIterable<Buffer> | Iterable<UploadBufferInfo> | AsyncIterable<UploadBufferInfo>,
name: string,
options?: DT.UploadStillEncodingOptions
): Promise<void> {
if (!this.state) return Promise.reject()
if (!this.state) throw new Error('Unable to check current resolution')
const resolution = Util.getVideoModeInfo(this.state.settings.videoMode)
if (!resolution) return Promise.reject()
const provideFrame = async function* (): AsyncGenerator<Buffer> {
if (!resolution) throw new Error('Failed to determine required resolution')

const provideFrame = async function* (): AsyncGenerator<UploadBufferInfo> {
for await (const frame of frames) {
yield Util.convertRGBAToYUV422(resolution.width, resolution.height, frame)
yield generateUploadBufferInfo(frame, resolution, !options?.disableRLE)
}
}
return this.dataTransferManager.uploadClip(index, provideFrame(), name, options)
return this.dataTransferManager.uploadClip(index, provideFrame(), name)
}

public async uploadAudio(index: number, data: Buffer, name: string): Promise<void> {
Expand Down
37 changes: 32 additions & 5 deletions src/dataTransfer/dataTransferUploadBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,45 @@ const debug = debug0('atem-connection:data-transfer:upload-buffer')
export interface UploadBufferInfo {
encodedData: Buffer
rawDataLength: number
isRleEncoded: boolean
hash: string | null
}

export function generateHashForBuffer(data: Buffer): string {
return data ? crypto.createHash('md5').update(data).digest('base64') : ''
}

export function generateBufferInfo(data: Buffer, shouldEncodeRLE: boolean): UploadBufferInfo {
return {
encodedData: shouldEncodeRLE ? Util.encodeRLE(data) : data,
rawDataLength: data.length,
hash: generateHashForBuffer(data),
export function generateUploadBufferInfo(
data: Buffer | UploadBufferInfo,
resolution: Util.VideoModeInfo,
shouldEncodeRLE: boolean
): UploadBufferInfo {
const expectedLength = resolution.width * resolution.height * 4
if (Buffer.isBuffer(data)) {
if (data.length !== expectedLength)
throw new Error(`Pixel buffer has incorrect length. Received ${data.length} expected ${expectedLength}`)

const encodedData = Util.convertRGBAToYUV422(resolution.width, resolution.height, data)

return {
encodedData: shouldEncodeRLE ? Util.encodeRLE(encodedData) : encodedData,
rawDataLength: encodedData.length,
isRleEncoded: shouldEncodeRLE,
hash: generateHashForBuffer(encodedData),
}
} else {
const result: UploadBufferInfo = { ...data }
if (data.rawDataLength !== expectedLength)
throw new Error(
`Pixel buffer has incorrect length. Received ${data.rawDataLength} expected ${expectedLength}`
)

if (shouldEncodeRLE && !data.isRleEncoded) {
data.isRleEncoded = true
data.encodedData = Util.encodeRLE(data.encodedData)
}

return result
}
}

Expand Down
21 changes: 6 additions & 15 deletions src/dataTransfer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DataTransferDownloadMacro } from './dataTransferDownloadMacro'
import { DataTransferUploadMacro } from './dataTransferUploadMacro'
import { LockObtainedCommand, LockStateUpdateCommand } from '../commands/DataTransfer'
import debug0 from 'debug'
import { generateBufferInfo } from './dataTransferUploadBuffer'
import type { UploadBufferInfo } from './dataTransferUploadBuffer'

const MAX_PACKETS_TO_SEND_PER_TICK = 50
const MAX_TRANSFER_INDEX = (1 << 16) - 1 // Inclusive maximum
Expand Down Expand Up @@ -170,30 +170,21 @@ export class DataTransferManager {
}
}

public async uploadStill(
index: number,
data: Buffer,
name: string,
description: string,
options?: UploadStillEncodingOptions
): Promise<void> {
const buffer = generateBufferInfo(data, !options?.disableRLE)
const transfer = new DataTransferUploadStill(index, buffer, name, description)
public async uploadStill(index: number, data: UploadBufferInfo, name: string, description: string): Promise<void> {
const transfer = new DataTransferUploadStill(index, data, name, description)
return this.#stillsLock.enqueue(transfer)
}

public async uploadClip(
index: number,
data: Iterable<Buffer> | AsyncIterable<Buffer>,
name: string,
options?: UploadStillEncodingOptions
data: Iterable<UploadBufferInfo> | AsyncIterable<UploadBufferInfo>,
name: string
): Promise<void> {
const provideFrame = async function* (): AsyncGenerator<DataTransferUploadClipFrame, undefined> {
let id = -1
for await (const frame of data) {
id++
const buffer = generateBufferInfo(frame, !options?.disableRLE)
yield new DataTransferUploadClipFrame(index, id, buffer)
yield new DataTransferUploadClipFrame(index, id, frame)
}
return undefined
}
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ import * as InputState from './state/input'
import * as MacroState from './state/macro'
import * as SettingsState from './state/settings'
export { VideoState, AudioState, MediaState, InfoState, InputState, MacroState, SettingsState }
export { UploadStillEncodingOptions } from './dataTransfer'
export type { UploadStillEncodingOptions } from './dataTransfer'
export type { UploadBufferInfo } from './dataTransfer/dataTransferUploadBuffer'

0 comments on commit 9e91923

Please sign in to comment.