/* * libmedia seek in bytes with timestamp * * 版权所有 (C) 2024 赵高兴 * Copyright (C) 2024 Gaoxing Zhao * * 此文件是 libmedia 的一部分 * This file is part of libmedia. * * libmedia 是自由软件;您可以根据 GNU Lesser General Public License(GNU LGPL)3.1 * 或任何其更新的版本条款重新分发或修改它 * libmedia is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.1 of the License, or (at your option) any later version. * * libmedia 希望能够为您提供帮助,但不提供任何明示或暗示的担保,包括但不限于适销性或特定用途的保证 * 您应自行承担使用 libmedia 的风险,并且需要遵守 GNU Lesser General Public License 中的条款和条件。 * libmedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ import type { AVIFormatContext } from '../AVFormatContext' import { getBytesByDuration } from './getBytesByDuration' import { type AVPacket, type AVStream, IOFlags, createAVPacket, destroyAVPacket, unrefAVPacket, NOPTS_VALUE_BIGINT, avRescaleQ, avRescaleQ2, errorType, AVPacketFlags } from '@libmedia/avutil' import { AV_MILLI_TIME_BASE_Q } from '@libmedia/avutil/internal' import { logger } from '@libmedia/common' import { IOError } from '@libmedia/common/io' export default async function seekInBytes( context: AVIFormatContext, stream: AVStream, timestamp: int64, firstPacketPos: int64, readAVPacket: (context: AVIFormatContext, avpacket: pointer) => Promise, syncAVPacket: (context: AVIFormatContext) => Promise ) { const now = context.ioReader.getPos() const fileSize = await context.ioReader.fileSize() let pos: int64 = NOPTS_VALUE_BIGINT let duration = timestamp if (stream.startTime !== NOPTS_VALUE_BIGINT) { duration -= stream.startTime } else { duration -= stream.firstDTS } const pointPts = avRescaleQ(timestamp, stream.timeBase, AV_MILLI_TIME_BASE_Q) // 头十秒直接回到开始位置 if (pointPts < 10000n) { logger.debug(`seek pts is earlier then 10s, seek to first packet pos(${firstPacketPos}) directly`) await context.ioReader.seek(firstPacketPos) return now } let bytes = getBytesByDuration(context.streams, duration, stream.timeBase) // 最大到结尾往前 10 秒 const max = fileSize - getBytesByDuration(context.streams, 10000n, AV_MILLI_TIME_BASE_Q) const length = getBytesByDuration(context.streams, 10000n, AV_MILLI_TIME_BASE_Q) if (bytes > max) { bytes = max } if (bytes < firstPacketPos) { await context.ioReader.seek(firstPacketPos) return now } const avpacket = createAVPacket() let seekMax = fileSize let seekMin = 0n failed: while (true) { if (seekMax - seekMin < length) { bytes = seekMin } await context.ioReader.seek(bytes) await syncAVPacket(context) if (context.ioReader.flags & IOFlags.ABORT) { break } let ret = await readAVPacket(context, avpacket) let now = avpacket.pos if (ret >= 0) { const currentPts = avRescaleQ2(avpacket.pts, addressof(avpacket.timeBase), AV_MILLI_TIME_BASE_Q) let diff = currentPts - pointPts logger.debug(`try to seek to pos: ${bytes}, got packet pts: ${avpacket.pts}(${currentPts}ms), diff: ${diff}ms`) // seek 时间戳的前面 10 秒内 if (diff <= 0n && -diff < 10000n || seekMax - seekMin < length) { // 查找最近的关键帧 const keyPos: int64[] = [now] while (diff <= 0) { if (avpacket.streamIndex === stream.index && (avpacket.flags & AVPacketFlags.AV_PKT_FLAG_KEY)) { keyPos.push(now) } unrefAVPacket(avpacket) ret = await readAVPacket(context, avpacket) if (ret < 0) { if (ret === IOError.END) { break } else if (ret === IOError.AGAIN) { continue } // 失败了重新 seek 回原来的位置 else { pos = NOPTS_VALUE_BIGINT break failed } } now = avpacket.pos diff = avRescaleQ2(avpacket.pts, addressof(avpacket.timeBase), AV_MILLI_TIME_BASE_Q) - pointPts } pos = keyPos.pop() break } // seek 后面 else if (diff > 0n) { seekMax = bytes bytes = (seekMin + seekMax) >> 1n } // seek 前面 10 秒外 else { seekMin = bytes bytes = (seekMin + seekMax) >> 1n } } else { // 失败了重新 seek 回原来的位置 pos = NOPTS_VALUE_BIGINT break } if (context.ioReader.flags & IOFlags.ABORT) { break } } destroyAVPacket(avpacket) if (pos !== NOPTS_VALUE_BIGINT) { logger.debug(`finally seek to pos ${pos}`) await context.ioReader.seek(pos) await syncAVPacket(context) return now } else { await context.ioReader.seek(now) if (context.ioReader.flags & IOFlags.ABORT) { return static_cast(errorType.EOF) } } return static_cast(errorType.FORMAT_NOT_SUPPORT) }