Skip to content

Commit

Permalink
vmaf impl refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
vpalmisano committed Apr 15, 2024
1 parent 7906b94 commit 2a38805
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 62 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,12 @@ DEBUG_LEVEL=DEBUG:* yarn start \
## Using the VMAF calculator
1. Run the tool adding the following options:
```sh
--script-params="{timestampWatermark:true,saveVideoTrack:1}"
--script-params="{timestampWatermarkVideo:true,saveRecvVideoTrack:1}"
--server-port=5000
--server-use-https=true
--server-data=/data
```
With `saveVideoTrack` you can specify the sessions that will be saved at
With `saveRecvVideoTrack` you can specify the sessions that will be saved at
receiver side (in this example `1` will save all the streams received in the
sessions with index `0` and `1`).
2. The sent/received videos will be saved in the `/data` directory.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@
"sprintf-js": "^1.1.3",
"tar-fs": "^2.1.1",
"terser": "^5.27.0",
"tesseract.js": "^5.0.4",
"tesseract.js": "^5.0.5",
"uuid": "^9.0.1",
"word-wrap": "^1.2.5",
"ws": "^8.16.0",
"yaml": "^2.3.4"
"yaml": "^2.4.1"
},
"devDependencies": {
"@types/browserify": "^12.0.40",
Expand Down
4 changes: 2 additions & 2 deletions scripts/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,14 +453,14 @@ window.streamWriter = async (
}/?auth=${window.SERVER_SECRET}&action=write-stream&filename=${filename}`,
)

if (filename.endsWith('.ivf')) {
if (filename.endsWith('.ivf.raw')) {
writeIvfHeader(ws, width, height, frameRate, fourcc)
}

return {
write(frameData, pts = 0) {
//log('write', filename, frameData.byteLength, pts)
if (filename.endsWith('.ivf')) {
if (filename.endsWith('.ivf.raw')) {
const data = new ArrayBuffer(12 + frameData.byteLength)
const view = new DataView(data)
view.setUint32(0, frameData.byteLength, true)
Expand Down
2 changes: 1 addition & 1 deletion scripts/get-user-media.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
log(`collectMediaTracks error:`, err)
}

if (enabledForSession(window.PARAMS?.timestampWatermark)) {
if (enabledForSession(window.PARAMS?.timestampWatermarkVideo)) {
mediaStream = applyTimestampWatermark(mediaStream)
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/peer-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ window.RTCPeerConnection = function (conf, options) {
handleTransceiverForInsertableStreams(id, transceiver)
}
if (receiver.track.kind === 'video') {
if (enabledForSession(window.PARAMS?.timestampWatermark)) {
if (enabledForSession(window.PARAMS?.timestampWatermarkVideo)) {
window.recognizeTimestampWatermark(
receiver.track,
({ timestamp, delay }) => {
Expand Down
4 changes: 2 additions & 2 deletions scripts/save-tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ window.saveVideoTrack = async (
const width = window.VIDEO_WIDTH
const height = window.VIDEO_HEIGHT
const frameRate = window.VIDEO_FRAMERATE
const fname = `${getParticipantNameForSave()}_${sendrecv}_${track.id}.ivf`
const fname = `${getParticipantNameForSave()}_${sendrecv}_${track.id}.ivf.raw`
log(`saveVideoTrack ${fname} ${width}x${height} ${frameRate}fps`)
const writer = await streamWriter(fname, width, height, frameRate, 'MJPG')

Expand Down Expand Up @@ -101,7 +101,7 @@ window.saveAudioTrack = async (track, sendrecv, enableDelay = 0) => {
}, Math.max(enableDelay - window.webrtcPerfElapsedTime(), 0))
}

const fname = `${getParticipantNameForSave()}-${sendrecv}_${
const fname = `${getParticipantNameForSave()}_${sendrecv}_${
track.id
}.f32le.raw`
log(`saveAudioTrack ${fname}`)
Expand Down
1 change: 1 addition & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ export async function resolveIP(
ip: string,
cacheTime = 60000,
): Promise<string> {
if (!ip) return ''
if (ipaddrJs.parse(ip).range() === 'private') {
return ip
}
Expand Down
96 changes: 52 additions & 44 deletions src/vmaf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,40 @@ export async function fixIvfFrames(fpath: string, outDir: string) {
return { participantDisplayName, outFilePath, startPts }
}

export async function fixIvfFiles(
vmafPath: string,
vmafKeepSourceFiles = true,
) {
const files = await await getFiles(vmafPath, '.ivf.raw')
log.debug(`fixIvfFiles files=${files}`)

const reference = new Map<string, string>()
const degraded = new Map<string, string[]>()
for (const filePath of files) {
try {
const { participantDisplayName, outFilePath } = await fixIvfFrames(
filePath,
vmafPath,
)
if (outFilePath.includes('_recv-by_')) {
if (!degraded.has(participantDisplayName)) {
degraded.set(participantDisplayName, [])
}
degraded.get(participantDisplayName)?.push(outFilePath)
} else {
reference.set(participantDisplayName, outFilePath)
}
if (!vmafKeepSourceFiles) {
await fs.promises.unlink(filePath)
}
} catch (err) {
log.error(`fixIvfFrames error: ${(err as Error).message}`)
}
}

return { reference, degraded }
}

export type VmafScore = {
sender: string
receiver: string
Expand All @@ -325,7 +359,7 @@ export type VmafScore = {
harmonic_mean: number
}

async function runVmaf(
export async function runVmaf(
referencePath: string,
degradedPath: string,
preview: boolean,
Expand All @@ -351,15 +385,21 @@ async function runVmaf(
ptsDiff,
})

const textHeight = Math.ceil(height / 18) + 6
const filter = `\
[0:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,crop=${width}:${
height - textHeight * 2
}:0:${textHeight},fps=fps=${frameRate},split[deg1][deg2];\
[1:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,crop=${width}:${
height - textHeight * 2
}:0:${textHeight},fps=fps=${frameRate},split[ref1][ref2];\
[deg1][ref1]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${vmafLogPath}:n_subsample=1:n_threads=${cpus}[vmaf]`

const cmd = preview
? `ffmpeg -loglevel warning -y -threads ${cpus} \
-i ${degradedPath} \
-ss ${ptsDiff / frameRate} -i ${referencePath} \
-filter_complex "\
[0:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,fps=fps=${frameRate},split[deg1][deg2];\
[1:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,fps=fps=${frameRate},split[ref1][ref2];\
[deg1][ref1]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${vmafLogPath}:n_subsample=1:n_threads=${cpus}[vmaf];\
[ref2][deg2]hstack[stacked]" \
-filter_complex "${filter};[ref2][deg2]hstack[stacked]" \
-map [vmaf] -f null - \
-map [stacked] -c:v libx264 -crf 20 -f mp4 -movflags +faststart ${
comparisonPath + '.mp4'
Expand All @@ -368,10 +408,7 @@ async function runVmaf(
: `ffmpeg -loglevel warning -y -threads ${cpus} \
-i ${degradedPath} \
-ss ${ptsDiff / frameRate} -i ${referencePath} \
-filter_complex "\
[0:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,fps=fps=${frameRate}[deg];\
[1:v]scale=w=${width}:h=${height}:flags=bicubic:eval=frame,fps=fps=${frameRate}[ref];\
[deg][ref]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${vmafLogPath}:n_subsample=1:n_threads=${cpus}[vmaf]" \
-filter_complex "${filter}" \
-map [vmaf] -f null - \
`

Expand Down Expand Up @@ -468,11 +505,6 @@ type VmafConfig = {
vmafKeepSourceFiles: boolean
}

async function getVmafFiles(dir: string): Promise<string[]> {
const files = await getFiles(dir, '.ivf')
return files.filter(f => !path.dirname(f).startsWith('vmaf/'))
}

export async function calculateVmafScore(
config: VmafConfig,
): Promise<VmafScore[]> {
Expand All @@ -487,34 +519,10 @@ export async function calculateVmafScore(
}
log.debug(`calculateVmafScore referencePath=${vmafPath}`)

const files = await getVmafFiles(vmafPath)
log.debug(`calculateVmafScore files=${files}`)
const outPath = path.join(vmafPath, 'vmaf')
await fs.promises.mkdir(outPath, { recursive: true })

const reference = new Map<string, string>()
const degraded = new Map<string, string[]>()
for (const filePath of files) {
try {
const { participantDisplayName, outFilePath } = await fixIvfFrames(
filePath,
outPath,
)
if (outFilePath.includes('_recv-by_')) {
if (!degraded.has(participantDisplayName)) {
degraded.set(participantDisplayName, [])
}
degraded.get(participantDisplayName)?.push(outFilePath)
} else {
reference.set(participantDisplayName, outFilePath)
}
if (!vmafKeepSourceFiles) {
await fs.promises.unlink(filePath)
}
} catch (err) {
log.error(`fixIvfFrames error: ${(err as Error).message}`)
}
}
const { reference, degraded } = await fixIvfFiles(
vmafPath,
vmafKeepSourceFiles,
)

const ret: VmafScore[] = []
for (const participantDisplayName of reference.keys()) {
Expand All @@ -541,7 +549,7 @@ export async function calculateVmafScore(
}
}
await fs.promises.writeFile(
path.join(outPath, 'vmaf.json'),
path.join(vmafPath, 'vmaf.json'),
JSON.stringify(ret, undefined, 2),
)

Expand Down
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7805,10 +7805,10 @@ tesseract.js-core@^5.0.0:
resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-5.0.0.tgz#159089b2e8a8c6b1f285134b1f7da6fc56914225"
integrity sha512-lJur5LzjinW5VYMKlVNnBU2JPLpO+A9VqAYBeuV+ZgH0hKvsnm+536Yyp+/zRTBdLe7D6Kok0FN9g+TE4J8qGA==

tesseract.js@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-5.0.4.tgz#8f18d299cac7600e51eab53b4402202402b2c1e0"
integrity sha512-GCIoSQMZlvTP2AaHrjUOH29/oyO7ZyHVe+BhTexEcO7/nDClRVDRjl2sYJLOWSSNbTDrm5q2m1+gfaf3lUrZ5Q==
tesseract.js@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-5.0.5.tgz#a974f4e1028350ebdcc8b5ebf70cea3ed2bc96de"
integrity sha512-xtTfec4IynE63sl6kAFkGl1mejlNxr9qQXzVGAUHd7IPdQXveopjGO9Eph6xkSuW5sUCC9AT6VdBmODh8ZymGg==
dependencies:
bmp-js "^0.1.0"
idb-keyval "^6.2.0"
Expand Down Expand Up @@ -8525,10 +8525,10 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==

yaml@^2.3.4:
version "2.3.4"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
yaml@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed"
integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==

yargs-parser@^20.2.7:
version "20.2.9"
Expand Down

0 comments on commit 2a38805

Please sign in to comment.