【问题标题】:AS3 NetStream AppendBytes Seek issueAS3 NetStream AppendBytes 寻求问题
【发布时间】:2015-08-24 12:40:14
【问题描述】:

我在使用 AS3 中的 NetStream 时遇到问题。我正在进行的项目允许用户浏览视频(本地)并播放。我遇到的问题是netStream.seek(0); 据我所知它没有做任何事情,尽管我进入了一个 NetStatusEvent 函数并触发了NetStream.Seek.Notify。我使用的是 NativeProcess,下面的函数有什么不同。

public function ProgressEventOutputHandler(e:ProgressEvent):void {
    videoByteArray = new ByteArray();
    nativeProcess.standardOutput.readBytes(videoByteArray, 0, nativeProcess.standardOutput.bytesAvailable);
    netStream.appendBytes(videoByteArray);
}

我在这里遗漏了什么吗?我在使用 netStream.seek(0); 之前暂停了 netStream。

编辑:

为了解决这个问题,我按照 VC.One 的说明进行了以下操作:

  • videoByteArray = new ByteArray(); 移至我的init 函数,并在此函数中创建tempVideoByteArray = new ByteArray();

  • 更新我的 ProgressEventOutputHandler 函数,使其不再为 videoByteArray 创建新的 ByteArray 并更改此行 - nativeProcess.standardOutput.readBytes(videoByteArray, videoByteArray.length, nativeProcess.standardOutput.bytesAvailable);

我没有更改任何其他内容,现在视频无法加载。如果我允许在 ProgressEventOutputHandler 函数中创建一个新的 ByteArray,视频会再次加载。

【问题讨论】:

  • 该代码设置对于查找来说并不灵活。您应该在函数之外创建videoStream 仅一次,然后在每个进度事件中填充它(当数据来自 FFMPEG 时,它将触发多次,这里就像您销毁最后一个数据并制作一个新的。好吧,但是想象一下最后的 progressEvent 给出了 5 分钟视频的最后 10 秒,现在当你的(新)字节只保留最后 10 秒时,你怎么能找到 2 分钟呢?如果你只填充一个 byteArray 它将包含所有字节,您可以在其中查找。
  • 你真的需要两个字节数组。 Bytes One 包含完整的字节,并且会随着它们的进入而增长。像xxxx.readBytes(videoStream, videoStream.length, xxxx.bytesAvailable); 这里的 xxxx 只是为了缩短代码,但你知道我的意思。 Bytes Two 是一个“临时”缓冲区,您可以用特定的帧字节填充并附加。 Bytes Two 可以通过示例 temp_BA.clear(); 清除以重新使用,因此无需使用 = new..,因为这实际上将多个字节数组添加到内存中,所有这些字节数组都称为相同的名称(编译器只是将它们视为 1000 个唯一引用 ID)。
  • 无论如何我的意思是......寻找appendBytes是自我控制的。您决定输入哪些字节,以便从videoStream 中选择正确的字节范围并复制到temp_BA,然后附加 temp_BA。想去别处寻找?清除 temp_BA 并再次从 videoStream 中抓取。您使用两个,以便主要的 videoStream 保持不变,以避免在“调整它”的同时也提供它等问题。我稍后会详细说明.. 寻找关键帧的字节并附加它
  • 更新问题以包含 VC.One 的 cmets
  • @AntBirch 你试过NetStream.appendBytesAction() 吗?另外,看看这个question

标签: actionscript-3 flash ffmpeg air netstream


【解决方案1】:

短版:

试试我贴在这里的代码:Github Snippetlink

加长版:

这个有点长,但希望它一劳永逸...不要担心砖墙的事情,墙壁是用来砸的。为了让您受到启发,请使用appendBytes 查看 VC:One 实验室的一些内部演示:

PS:我将使用URLStream 方法,因为对于那些加载本地或在线文件的人来说,这是一个更有用的答案。您可以从 urlstream.progressEvent 更改为通常的 nativeProcess.progressEvent
我知道 FFMPEG,但只使用 AIR 来制作 Android 应用程序。所以对于这个 AIR/FFMPEG 连接,你比我更了解。

此答案还假设您使用 FLV 与 MPEG H.264 视频和 MP3 或 AAC 音频

ffmpeg -i input.mp4 -c:v copy -c:a mp3 -b:a 128k -ac 2 -ar 44100 FLV_with_MP3.flv

这个假设很重要,因为它会影响我们寻找的字节类型。 对于上述带有 H.264 视频和 AAC 或 MP3 音频的 FLV,我们可以预期以下内容(搜索时):

  • 由于这是 MPEG,第一个视频标签将保存 AVC 解码器配置字节,第一个音频标签保存 音频特定配置字节。该数据不是实际的媒体帧,而是像音频/视频标签一样简单地打包。这些是 MPEG 播放所必需的。可以在 MP4 容器内的 STSD 元数据条目 (MOOV atom) 中找到相同的字节。现在下一个找到的视频标签将(或应该)是视频的实际第一帧。
  • 视频关键帧:从 0x09 开始,接下来的第 11 个字节是 0x17,第 12 个字节是 0x01
  • Audio TAG AAC :从 0x08 开始,接下来的第 11 个字节是 0xAF,第 12 个字节是 0x01
  • Audio TAG MP3 :从 0x08 开始,接下来的第 11 个字节是 0x2F,第 12 个字节是 0xFF

1) 字节复制和检查值:

您正在寻找代表视频“标签”的字节。除了元数据标签,您现在可以期望“标签”表示音频或视频帧的容器。有两种方法可以将标记字节放入“临时字节数组”(我们将其命名为 temp_BA)。

  • ReadBytes(慢):在source_BA 的开始/结束范围内提取单个字节值
  • WriteBytes(快速):即时复制来自source_BA 的字节的开始/结束范围

Readbytes 解释告诉 Source 将其字节读入 Target。源将从其当前偏移量(位置)向前读取长度。 在继续阅读之前转到正确的源位置...

source_BA.readBytes( into Target_BA, Pos within Target_BA, length of bytes required );

执行上述行后,源位置现在将已经前进以说明新的行进长度。 (公式:Source new Pos = previousPos + BytesLengthRequired)。

Writebytes 解释告诉 Target 复制 来自 Source 的一系列字节。由于从已知信息(来自 Source)复制,速度很快。 目标从当前位置开始写入...

target_BA.writeBytes( from source_BA, Pos within source_BA, length of bytes required );

执行上述行后,请注意 Source 和目标位置不变

使用上述方法从特定的source_BA.position = x 获取所需的标记字节到temp_BA

要检查任何字节(其值),请使用以下方法更新int 类型的某些变量:

注意:不要将.readByte(); 与类似的.readBytes() 混淆,.readByte(); 提取一个数值(字节),.readBytes() 将一大块字节复制到另一个字节数组。

2) 寻找视频关键帧(或 I 帧):

[带有关键帧 H264/AAC 的 Video TAG 插图图像]

寻找视频关键帧

  • 从起始偏移量开始,使用while 循环现在[向前] 遍历字节,在每个字节中搜索“9”的one-byte value(十六进制:0x09),找到后,我们会进一步检查前面的字节,以确认它确实是一个真正的关键帧,而不仅仅是随机出现的“9”。
  • 在 H.264 视频编解码器的情况下,在正确的“9”字节位置 (xPos),我们预计前面的 第 11 和第 12 字节总是17”和“01”。
  • If== true 然后我们检查三个 Tag Size 字节并将 15 添加到此整数中,以获得预期从 Source 写入 Target 的字节总长度(temp_BA)。我们添加了 15 来说明 before 的 11 个字节以及 预期 TAG DATA 的 4 个字节。标签结尾的这 4 个字节是“Previous Tag Size”,这个数量实际上包括前 11 个字节,但不包括这 4 个字节本身。
  • 我们告诉temp_BASource(你的videoByteArray写入字节9" 字节 (xPos),长度为“标记大小”+ 15。您现在已经提取了一个 MPEG 关键帧。
    示例temp_BA.writeBytes( videoByteArray, int (xPos), int (TAG_size) );
  • 这个带有关键帧标签的 temp_BA 现在可以使用以下方法附加:
    示例netStream.appendBytes( temp_BA ); //displays a single frame

注意:为了读取 3 个字节的标记大小,我将展示一个自定义转换 bytes_toInt() 函数(因为处理器一次读取 1、2 或 4 个字节的整数,所以这里读取 3 个字节是一个不公平的请求)。

搜索提示:标签总是相互跟随。我们还可以通过检查字节是否用于非关键帧(P 帧)视频标签甚至某些音频标签来更快地寻找。如果是这样,那么我们检查那个特定的tag size,现在增加我们的xPos 来跳转这个新的长度。这样我们可以跳过整个标签大小,而不仅仅是单个字节。仅当我们有关键帧标签时才停止。

3) 从视频关键帧回放:

细想一下,播放就像是逐帧进行的自动搜索。获取每个下一帧的预期速度由视频的编码帧速率定义。

所以你的播放功能可以简单地是一个Timer,每秒(或1000毫秒)获取X数量的视频标签(帧)。您以my_Timer = new Timer ( video_FPS ) 为例。当计时器运行并达到每秒的每个 FPS 片段时,它将运行 append_PLAY(); 函数,该函数又运行 get_frame_Tag(); 函数。

  • NS.seek(0) :将 NetStream 置于“搜索模式”。 (数字无关紧要,但必须存在于命令中)。任何“超前帧”缓冲区都会被清除,直到......
  • RESET_SEEK :结束“搜索模式”,现在允许图像更新。使用RESET_SEEK 命令后附加的第一个标签必须是带有视频关键帧的标签。 (对于纯音频,这可以是任何标签,因为从技术上讲,所有音频标签都是音频关键帧)
  • END_SEQUENCE :(对于 MPEG H.264)播放任何剩余的“提前帧”(耗尽缓冲区)。一旦耗尽,您现在可以附加任何类型的视频标签。请记住 H.264 需要向前移动的时间戳,如果您看到 f**ked up 像素,那么您的下一个标签时间戳是错误的(太高或太低)。如果您只附加一帧(海报图片?),您可以使用END_SEQUEMCE 来排空缓冲区并显示该一帧(无需等待缓冲区先填满 x 帧)...

play 函数充当中间人函数来管理事物,而不会将 get frame 函数与If 语句等混淆。管理事物意味着例如检查根据标签大小,有足够的字节下载甚至开始获取帧。

4) 工作示例的源代码:

代码太长.. 请参阅下面的linkhttps://gist.github.com/Valerio-Charles-VC1/657054b773dba9ba1cbc

希望对您有所帮助。风险投资

【讨论】:

  • 嗨蚂蚁,如果你看到这个...这篇文章是正确的(感谢接受)但我认为它需要精简一下,嗯?稍后还将更新演示的链接。不得不删除它们以保护代码,但是当我将来想出一些东西时它们会出现(即:何时删除此评论)
猜你喜欢
  • 2012-12-31
  • 2013-05-11
  • 2011-04-19
  • 2014-12-29
  • 1970-01-01
  • 2011-05-05
  • 2011-01-19
  • 2011-06-28
  • 1970-01-01
相关资源
最近更新 更多