feat:v1版本完成
This commit is contained in:
parent
369d35cd2e
commit
023fe0a0f3
@ -69,8 +69,8 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler {
|
||||
val textureEntry = textureRegistry.createSurfaceTexture()
|
||||
textureId = textureEntry.id()
|
||||
decoder = VideoDecoder(context, textureEntry, width, height, codecType) {
|
||||
// onFrameAvailable callback
|
||||
channel.invokeMethod("onFrameAvailable", mapOf("textureId" to textureId))
|
||||
// onFrameRendered callback
|
||||
channel.invokeMethod("onFrameRendered", mapOf("textureId" to textureId))
|
||||
}
|
||||
result.success(textureId)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@ -24,9 +24,9 @@ import android.os.SystemClock
|
||||
* 主要职责:
|
||||
* 1. 初始化并配置Android MediaCodec解码器,支持H264/H265视频流解码。
|
||||
* 2. 管理解码输入帧队列,将解码后的视频帧渲染到Surface。
|
||||
* 3. 支持同步与异步解码模式,自动处理输入/输出缓冲区。
|
||||
* 3. 支持多线程解码与渲染,解耦数据流。
|
||||
* 4. 负责解码器的生命周期管理(启动、释放等)。
|
||||
* 5. 通过回调通知Flutter端有新帧可用。
|
||||
* 5. 通过回调通知Flutter端有新帧渲染。
|
||||
*
|
||||
* 构造参数说明:
|
||||
* - context: Android上下文
|
||||
@ -34,23 +34,7 @@ import android.os.SystemClock
|
||||
* - width: 视频宽度
|
||||
* - height: 视频高度
|
||||
* - codecType: 编解码器类型("h264"或"h265")
|
||||
* - onFrameAvailable: 新帧渲染回调
|
||||
*
|
||||
* 主要成员变量:
|
||||
* - surfaceTexture/surface: 视频渲染目标
|
||||
* - mediaCodec: Android MediaCodec解码器实例
|
||||
* - inputFrameQueue: 输入帧队列,支持并发
|
||||
* - running: 解码器运行状态
|
||||
* - frameSeqSet: 用于去重的线程安全Set,防止重复帧入队
|
||||
* - outputFrameQueue: 解码输出缓冲区,容量为10帧
|
||||
* - renderThreadRunning: 渲染线程控制
|
||||
* - renderThread: 渲染线程
|
||||
* - mainHandler: 主线程Handler,用于安全切换onFrameAvailable到主线程
|
||||
* - renderFps: 渲染帧率(fps),可由外部控制,默认15
|
||||
*
|
||||
* 主要方法:
|
||||
* - decodeFrame: 向解码器输入一帧数据
|
||||
* - release: 释放解码器和相关资源
|
||||
* - onFrameRendered: 开始解码并且渲染成功后的回调
|
||||
*/
|
||||
class VideoDecoder(
|
||||
context: Context,
|
||||
@ -58,40 +42,40 @@ class VideoDecoder(
|
||||
width: Int,
|
||||
height: Int,
|
||||
codecType: String,
|
||||
private val onFrameAvailable: () -> Unit
|
||||
private val onFrameRendered: () -> Unit
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "VideoDecoder"
|
||||
private const val TIMEOUT_US = 10000L
|
||||
private const val INPUT_BUFFER_QUEUE_CAPACITY = 50
|
||||
private const val INPUT_BUFFER_QUEUE_CAPACITY = 250 // 输入缓冲区容量
|
||||
}
|
||||
|
||||
// region 成员变量定义
|
||||
|
||||
// SurfaceTexture与Surface用于视频渲染
|
||||
private val surfaceTexture: SurfaceTexture = textureEntry.surfaceTexture()
|
||||
private val surface: Surface = Surface(surfaceTexture)
|
||||
private var mediaCodec: MediaCodec? = null
|
||||
private val inputFrameQueue = LinkedBlockingQueue<FrameData>(INPUT_BUFFER_QUEUE_CAPACITY)
|
||||
private var running = true
|
||||
private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap<Int, Boolean>())
|
||||
|
||||
// 解码输出缓冲区,容量为10帧
|
||||
private val outputFrameQueue = LinkedBlockingQueue<DecodedFrame>(50)
|
||||
// 输入帧队列,支持并发,容量较大以防止丢帧
|
||||
private val inputFrameQueue = LinkedBlockingQueue<FrameData>(INPUT_BUFFER_QUEUE_CAPACITY)
|
||||
private var running = true // 解码器运行状态
|
||||
private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap<Int, Boolean>()) // 防止重复帧入队
|
||||
|
||||
// 解码输出缓冲区,容量为100帧
|
||||
private val outputFrameQueue = LinkedBlockingQueue<DecodedFrame>(100)
|
||||
|
||||
// 渲染线程控制
|
||||
@Volatile private var renderThreadRunning = true
|
||||
private var renderThread: Thread? = null
|
||||
|
||||
// 主线程Handler,用于安全切换onFrameAvailable到主线程
|
||||
// 主线程Handler,用于安全切换onFrameRendered到主线程
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
// 首帧原始时间戳(微秒),用于归零
|
||||
private var firstTimestampUs: Long? = null
|
||||
// 上一帧归一化时间戳(微秒),用于误差兼容
|
||||
private var lastTimestampUs: Long = 0L
|
||||
// 容忍误差区间(15fps一帧时长,单位微秒)
|
||||
private val toleranceUs = 66000L
|
||||
|
||||
// 渲染帧率(fps),可由外部控制,默认15
|
||||
@Volatile var renderFps: Int = 20
|
||||
// 渲染帧率(fps),可由外部控制,默认18
|
||||
@Volatile var renderFps: Int = 18
|
||||
|
||||
// 输入帧结构体
|
||||
private data class FrameData(
|
||||
val data: ByteArray,
|
||||
val frameType: Int,
|
||||
@ -100,7 +84,7 @@ class VideoDecoder(
|
||||
val refIFrameSeq: Int?
|
||||
)
|
||||
|
||||
// 解码后帧结构,显式携带时间戳(单位:微秒)
|
||||
// 解码后帧结构体,显式携带时间戳(单位:微秒)
|
||||
private data class DecodedFrame(
|
||||
val codec: MediaCodec,
|
||||
val bufferIndex: Int,
|
||||
@ -108,19 +92,28 @@ class VideoDecoder(
|
||||
val timestampUs: Long // 帧时间戳,单位微秒
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region 初始化与解码器配置
|
||||
init {
|
||||
// 配置Surface尺寸
|
||||
surfaceTexture.setDefaultBufferSize(width, height)
|
||||
// 选择MIME类型
|
||||
val mime = when (codecType) {
|
||||
"h264" -> "video/avc"
|
||||
"h265" -> "video/hevc"
|
||||
else -> "video/avc"
|
||||
}
|
||||
// 创建并配置MediaFormat
|
||||
val format = MediaFormat.createVideoFormat(mime, width, height)
|
||||
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height)
|
||||
// 创建解码器
|
||||
val decoder = MediaCodec.createDecoderByType(mime)
|
||||
// 设置解码回调
|
||||
decoder.setCallback(object : MediaCodec.Callback() {
|
||||
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
|
||||
if (!running) return
|
||||
// 从输入队列取出一帧数据
|
||||
val frame = inputFrameQueue.poll()
|
||||
if (frame != null) {
|
||||
frameSeqSet.remove(frame.frameSeq)
|
||||
@ -130,6 +123,7 @@ class VideoDecoder(
|
||||
inputBuffer.put(frame.data)
|
||||
val start = System.nanoTime()
|
||||
val ptsUs = frame.timestamp * 1000L
|
||||
// 入队到解码器
|
||||
codec.queueInputBuffer(
|
||||
index,
|
||||
0,
|
||||
@ -137,8 +131,6 @@ class VideoDecoder(
|
||||
ptsUs,
|
||||
if (frame.frameType == 0) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
|
||||
)
|
||||
val end = System.nanoTime()
|
||||
Log.d(TAG, "queueInputBuffer cost: "+ (end - start) + " ns, frameSeq="+frame.frameSeq+", type="+frame.frameType+", ptsUs="+ptsUs)
|
||||
} else {
|
||||
codec.queueInputBuffer(index, 0, 0, 0, 0)
|
||||
}
|
||||
@ -148,7 +140,7 @@ class VideoDecoder(
|
||||
}
|
||||
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
|
||||
if (!running) return
|
||||
// 将解码后帧放入输出缓冲区,由渲染线程处理
|
||||
// 解码后帧入输出缓冲区,由渲染线程处理
|
||||
val frame = DecodedFrame(codec, index, MediaCodec.BufferInfo().apply {
|
||||
set(0, info.size, info.presentationTimeUs, info.flags)
|
||||
}, info.presentationTimeUs)
|
||||
@ -156,7 +148,6 @@ class VideoDecoder(
|
||||
// 缓冲区满,丢弃最旧帧再插入
|
||||
outputFrameQueue.poll()
|
||||
outputFrameQueue.offer(frame)
|
||||
Log.w(TAG, "outputFrameQueue full, drop oldest frame")
|
||||
}
|
||||
}
|
||||
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
|
||||
@ -171,32 +162,32 @@ class VideoDecoder(
|
||||
// 启动渲染线程
|
||||
renderThreadRunning = true
|
||||
renderThread = Thread {
|
||||
var renderedFrameCount = 0 // 渲染帧计数器
|
||||
while (renderThreadRunning) {
|
||||
// 计算每帧渲染间隔
|
||||
val frameIntervalMs = if (renderFps > 0) 1000L / renderFps else 66L
|
||||
val loopStart = SystemClock.elapsedRealtime()
|
||||
try {
|
||||
val frame = outputFrameQueue.poll()
|
||||
if (frame != null) {
|
||||
Log.i(TAG, "[RenderThread] 定时渲染帧: frame.timestampUs=${frame.timestampUs}")
|
||||
val start = System.nanoTime()
|
||||
frame.codec.releaseOutputBuffer(frame.bufferIndex, true)
|
||||
val end = System.nanoTime()
|
||||
Log.d(TAG, "[RenderThread] releaseOutputBuffer cost: "+ (end - start) + " ns, frame.timestampUs=${frame.timestampUs}")
|
||||
// 确保onFrameAvailable在主线程执行,避免FlutterJNI线程异常
|
||||
mainHandler.post { onFrameAvailable() }
|
||||
} else {
|
||||
Log.d(TAG, "[RenderThread] 定时渲染无帧可用")
|
||||
// 阻塞式等待新帧,避免空转
|
||||
val frame = outputFrameQueue.take()
|
||||
frame.codec.releaseOutputBuffer(frame.bufferIndex, true)
|
||||
renderedFrameCount++
|
||||
// 每累计renderFps帧回调一次onFrameRendered
|
||||
if (renderedFrameCount >= renderFps) {
|
||||
// 回调到Flutter端 通知解码并渲染完81帧了
|
||||
mainHandler.post { onFrameRendered() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "[RenderThread] Exception", e)
|
||||
}
|
||||
// 控制渲染节奏
|
||||
val loopCost = SystemClock.elapsedRealtime() - loopStart
|
||||
val sleepMs = frameIntervalMs - loopCost
|
||||
if (sleepMs > 0) {
|
||||
try { Thread.sleep(sleepMs) } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
// 清理剩余帧
|
||||
// 清理剩余帧,防止内存泄漏
|
||||
while (true) {
|
||||
val frame = outputFrameQueue.poll() ?: break
|
||||
try {
|
||||
@ -206,7 +197,13 @@ class VideoDecoder(
|
||||
}
|
||||
renderThread?.start()
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 核心方法
|
||||
|
||||
/**
|
||||
* 向解码器输入一帧数据(所有类型均允许入队,去重)
|
||||
*/
|
||||
fun decodeFrame(
|
||||
frameData: ByteArray,
|
||||
frameType: Int,
|
||||
@ -215,13 +212,13 @@ class VideoDecoder(
|
||||
refIFrameSeq: Int?
|
||||
): Boolean {
|
||||
if (!running || mediaCodec == null) return false
|
||||
if (!frameSeqSet.add(frameSeq)) {
|
||||
return false
|
||||
}
|
||||
// 直接使用外部传入的递增时间戳,无需归零和对齐
|
||||
if (!frameSeqSet.add(frameSeq)) return false // 防止重复帧
|
||||
return inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放解码器和相关资源
|
||||
*/
|
||||
fun release() {
|
||||
running = false
|
||||
inputFrameQueue.clear()
|
||||
@ -239,4 +236,5 @@ class VideoDecoder(
|
||||
surface.release()
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@ -382,27 +382,22 @@ class _VideoViewState extends State<VideoView> {
|
||||
}
|
||||
}
|
||||
|
||||
// 解码帧
|
||||
Future<bool> _decodeNextFrame(H264Frame frame, int frameSeq) async {
|
||||
// 解码帧(已适配sendFrame,splitNalFromIFrame=false)
|
||||
Future<void> _decodeNextFrame(H264Frame frame, int frameSeq) async {
|
||||
if (_textureId == null || !_isInitialized || !_isPlaying) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
||||
final success = await VideoDecodePlugin.decodeFrame(
|
||||
await VideoDecodePlugin.sendFrame(
|
||||
frameData: frame.data,
|
||||
frameType: frame.frameType,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq,
|
||||
refIFrameSeq: frame.refIFrameSeq,
|
||||
splitNalFromIFrame: false,
|
||||
);
|
||||
if (!success) {
|
||||
_log("解码帧失败,索引 $frameSeq (type=${frame.frameType})");
|
||||
}
|
||||
return success;
|
||||
} catch (e) {
|
||||
_log("解码帧错误: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -517,12 +512,12 @@ class _VideoViewState extends State<VideoView> {
|
||||
}
|
||||
|
||||
final frame = _h264Frames[_currentFrameIndex];
|
||||
bool decodeSuccess = await _decodeNextFrame(frame, _currentFrameIndex);
|
||||
await _decodeNextFrame(frame, _currentFrameIndex);
|
||||
|
||||
// 只有在成功解码的情况下才显示日志信息
|
||||
if (!decodeSuccess && _enablePacketLoss) {
|
||||
_log("跳过索引 $_currentFrameIndex 的帧(丢帧模拟)");
|
||||
}
|
||||
// if (!decodeSuccess && _enablePacketLoss) {
|
||||
// _log("跳过索引 $_currentFrameIndex 的帧(丢帧模拟)");
|
||||
// }
|
||||
|
||||
// 无论解码是否成功,都移动到下一帧
|
||||
_currentFrameIndex++;
|
||||
|
||||
31
lib/frame_dependency_manager.dart
Normal file
31
lib/frame_dependency_manager.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// SPS/PPS/I帧依赖关系管理器
|
||||
class FrameDependencyManager {
|
||||
Uint8List? _sps;
|
||||
Uint8List? _pps;
|
||||
int? _lastIFrameSeq;
|
||||
|
||||
/// 更新SPS缓存
|
||||
void updateSps(Uint8List sps) {
|
||||
_sps = sps;
|
||||
}
|
||||
/// 更新PPS缓存
|
||||
void updatePps(Uint8List pps) {
|
||||
_pps = pps;
|
||||
}
|
||||
Uint8List? get sps => _sps;
|
||||
Uint8List? get pps => _pps;
|
||||
|
||||
/// 判断是否有可用I帧
|
||||
bool get hasIFrame => _lastIFrameSeq != null;
|
||||
int? get lastIFrameSeq => _lastIFrameSeq;
|
||||
void updateIFrameSeq(int seq) {
|
||||
_lastIFrameSeq = seq;
|
||||
}
|
||||
|
||||
/// 判断指定I帧序号是否为最近一次成功解码的I帧
|
||||
bool isIFrameDecoded(int? seq) {
|
||||
return seq != null && seq == _lastIFrameSeq;
|
||||
}
|
||||
}
|
||||
70
lib/nalu_utils.dart
Normal file
70
lib/nalu_utils.dart
Normal file
@ -0,0 +1,70 @@
|
||||
/// NALU相关工具类与结构体
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// NALU单元结构体
|
||||
class NaluUnit {
|
||||
final int type; // NALU类型
|
||||
final List<int> data;
|
||||
NaluUnit(this.type, this.data);
|
||||
}
|
||||
|
||||
class NaluUtils {
|
||||
/// 分离一帧数据中的所有NALU单元
|
||||
static List<NaluUnit> splitNalus(List<int> data) {
|
||||
final List<NaluUnit> nalus = [];
|
||||
int i = 0;
|
||||
List<int> startCodes = [];
|
||||
// 先找到所有起始码位置
|
||||
while (i < data.length - 3) {
|
||||
if (i < data.length - 4 &&
|
||||
data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1) {
|
||||
startCodes.add(i);
|
||||
i += 4;
|
||||
} else if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
|
||||
startCodes.add(i);
|
||||
i += 3;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// 补上结尾
|
||||
startCodes.add(data.length);
|
||||
// 分割NALU
|
||||
int nalusTotalLen = 0;
|
||||
for (int idx = 0; idx < startCodes.length - 1; idx++) {
|
||||
int start = startCodes[idx];
|
||||
int next = startCodes[idx + 1];
|
||||
int skip = (data[start] == 0 && data[start + 1] == 0 && data[start + 2] == 0 && data[start + 3] == 1) ? 4 : 3;
|
||||
int naluStart = start + skip;
|
||||
if (naluStart < next) {
|
||||
final nalu = data.sublist(start, next);
|
||||
nalusTotalLen += nalu.length;
|
||||
if (nalu.isNotEmpty) {
|
||||
nalus.add(NaluUnit(getNaluType(nalu), nalu));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nalus.isEmpty && data.isNotEmpty) {
|
||||
nalus.add(NaluUnit(getNaluType(data), data));
|
||||
} else if (nalusTotalLen < data.length) {
|
||||
nalus.add(NaluUnit(getNaluType(data.sublist(nalusTotalLen)), data.sublist(nalusTotalLen)));
|
||||
}
|
||||
return nalus;
|
||||
}
|
||||
|
||||
/// 获取NALU类型
|
||||
static int getNaluType(List<int> nalu) {
|
||||
if (nalu.isEmpty) return -1;
|
||||
int offset = 0;
|
||||
if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
|
||||
if (nalu[2] == 0x01)
|
||||
offset = 3;
|
||||
else if (nalu[2] == 0x00 && nalu[3] == 0x01)
|
||||
offset = 4;
|
||||
}
|
||||
if (nalu.length > offset) {
|
||||
return nalu[offset] & 0x1F;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'video_decode_plugin_platform_interface.dart';
|
||||
import 'nalu_utils.dart';
|
||||
import 'frame_dependency_manager.dart';
|
||||
|
||||
/// 视频解码器配置
|
||||
class VideoDecoderConfig {
|
||||
@ -41,20 +43,40 @@ class VideoDecodePlugin {
|
||||
|
||||
static int? _textureId;
|
||||
|
||||
/// onFrameRendered回调类型(解码并已开始渲染)
|
||||
static void Function(int textureId)? _onFrameRendered;
|
||||
|
||||
/// 设置onFrameRendered监听
|
||||
static void setOnFrameRenderedListener(
|
||||
void Function(int textureId) callback) {
|
||||
_onFrameRendered = callback;
|
||||
_channel.setMethodCallHandler(_handleMethodCall);
|
||||
}
|
||||
|
||||
static Future<void> _handleMethodCall(MethodCall call) async {
|
||||
if (call.method == 'onFrameRendered') {
|
||||
final int? textureId = call.arguments['textureId'];
|
||||
if (_onFrameRendered != null && textureId != null) {
|
||||
_onFrameRendered!(textureId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化解码器
|
||||
static Future<int?> initDecoder(VideoDecoderConfig config) async {
|
||||
final textureId = await _channel.invokeMethod<int>('initDecoder', config.toMap());
|
||||
final textureId =
|
||||
await _channel.invokeMethod<int>('initDecoder', config.toMap());
|
||||
_textureId = textureId;
|
||||
return textureId;
|
||||
}
|
||||
|
||||
/// 解码视频帧(参数扩展)
|
||||
static Future<bool> decodeFrame({
|
||||
/// 解码视频帧(参数扩展,仅供内部调用)
|
||||
static Future<bool> _decodeFrame({
|
||||
required Uint8List frameData,
|
||||
required int frameType, // 0=I帧, 1=P帧
|
||||
required int timestamp, // 毫秒或微秒
|
||||
required int frameSeq, // 帧序号
|
||||
int? refIFrameSeq, // P帧时可选
|
||||
required int frameSeq, // 帧序号
|
||||
int? refIFrameSeq, // P帧时可选
|
||||
}) async {
|
||||
if (_textureId == null) return false;
|
||||
final params = {
|
||||
@ -92,20 +114,115 @@ class VideoDecodePlugin {
|
||||
/// 获取默认纹理ID
|
||||
static int? get textureId => _textureId;
|
||||
|
||||
/// 注册插件(不需要手动调用)
|
||||
static void registerWith() {
|
||||
// 仅用于插件注册
|
||||
static final _depManager = FrameDependencyManager();
|
||||
|
||||
///
|
||||
/// [frameData]:帧数据
|
||||
/// [frameType]:帧类型 0=I帧, 1=P帧
|
||||
/// [timestamp]:帧时间戳(绝对时间戳)
|
||||
/// [frameSeq]:帧序号
|
||||
/// [splitNalFromIFrame]:true时遇到I帧自动从I帧分割NALU并依赖管理,false时直接发送原始数据(适配SPS/PPS/I帧独立推送场景)。
|
||||
///
|
||||
static Future<void> sendFrame({
|
||||
required List<int> frameData,
|
||||
required int frameType,
|
||||
required int timestamp,
|
||||
required int frameSeq,
|
||||
bool splitNalFromIFrame = false,
|
||||
}) async {
|
||||
if (splitNalFromIFrame && frameType == 0) {
|
||||
// 优先使用缓存的SPS/PPS
|
||||
if (_depManager.sps != null && _depManager.pps != null) {
|
||||
await _decodeFrame(
|
||||
frameData: _depManager.sps!,
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq - 2,
|
||||
refIFrameSeq: frameSeq - 2,
|
||||
);
|
||||
await _decodeFrame(
|
||||
frameData: _depManager.pps!,
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq - 1,
|
||||
refIFrameSeq: frameSeq - 1,
|
||||
);
|
||||
await _decodeFrame(
|
||||
frameData: Uint8List.fromList(frameData),
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq,
|
||||
refIFrameSeq: frameSeq,
|
||||
);
|
||||
_depManager.updateIFrameSeq(frameSeq);
|
||||
print('[VideoDecodePlugin] 发送I帧及SPS/PPS(缓存), frameSeq=$frameSeq');
|
||||
return;
|
||||
}
|
||||
// 首次无缓存时分割并缓存SPS/PPS
|
||||
final nalus = NaluUtils.splitNalus(frameData);
|
||||
print('[调试] frameSeq=$frameSeq, 分割出NALU数量=${nalus.length}');
|
||||
for (final nalu in nalus) {
|
||||
print('[调试] NALU type=${nalu.type}, length=${nalu.data.length}');
|
||||
}
|
||||
List<int>? sps, pps;
|
||||
for (final nalu in nalus) {
|
||||
if (nalu.type == 7) sps = nalu.data;
|
||||
else if (nalu.type == 8) pps = nalu.data;
|
||||
}
|
||||
if (sps != null) {
|
||||
print('[调试] SPS被缓存, 长度=${sps.length}');
|
||||
_depManager.updateSps(Uint8List.fromList(sps));
|
||||
}
|
||||
if (pps != null) {
|
||||
print('[调试] PPS被缓存, 长度=${pps.length}');
|
||||
_depManager.updatePps(Uint8List.fromList(pps));
|
||||
}
|
||||
if (_depManager.sps == null || _depManager.pps == null) {
|
||||
print('[VideoDecodePlugin] 丢弃I帧: 未缓存SPS/PPS');
|
||||
return;
|
||||
}
|
||||
await _decodeFrame(
|
||||
frameData: _depManager.sps!,
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq - 2,
|
||||
refIFrameSeq: frameSeq - 2,
|
||||
);
|
||||
await _decodeFrame(
|
||||
frameData: _depManager.pps!,
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq - 1,
|
||||
refIFrameSeq: frameSeq - 1,
|
||||
);
|
||||
await _decodeFrame(
|
||||
frameData: Uint8List.fromList(frameData),
|
||||
frameType: 0,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq,
|
||||
refIFrameSeq: frameSeq,
|
||||
);
|
||||
_depManager.updateIFrameSeq(frameSeq);
|
||||
print('[VideoDecodePlugin] 发送I帧及SPS/PPS(首次分割), frameSeq=$frameSeq');
|
||||
return;
|
||||
}
|
||||
// 兼容直接推送SPS/PPS/I帧/P帧等场景,直接发送
|
||||
await _decodeFrame(
|
||||
frameData: Uint8List.fromList(frameData),
|
||||
frameType: frameType,
|
||||
timestamp: timestamp,
|
||||
frameSeq: frameSeq,
|
||||
refIFrameSeq: frameType == 0 ? frameSeq : _depManager.lastIFrameSeq,
|
||||
);
|
||||
// 若为I帧,更新依赖管理
|
||||
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
|
||||
|
||||
// P帧依赖链完整性校验
|
||||
if (frameType == 1) {
|
||||
if (!_depManager.isIFrameDecoded(refIFrameSeq)) {
|
||||
print('[丢帧] P帧依赖的I帧未解码,丢弃 frameSeq=$frameSeq, refIFrameSeq=$refIFrameSeq');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 在Dart中实现简单的同步锁
|
||||
void synchronized(Object lock, Function() action) {
|
||||
// 在单线程的Dart中,我们不需要真正的锁
|
||||
// 但我们保留这个结构以便将来可能的改进
|
||||
action();
|
||||
}
|
||||
|
||||
/// 在同步锁中执行并返回结果的版本
|
||||
T synchronizedWithResult<T>(Object lock, T Function() action) {
|
||||
return action();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user