AS3 NetStream AppendBytes 查找问题
AS3 NetStream AppendBytes Seek issue
我在 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();
移动到我的初始化函数,并在此函数中创建了 tempVideoByteArray = new ByteArray();
。
更新我的 ProgressEventOutputHandler 函数,使其不再为 videoByteArray 创建新的 ByteArray 并更改此行 - nativeProcess.standardOutput.readBytes(videoByteArray, videoByteArray.length, nativeProcess.standardOutput.bytesAvailable);
我没有做任何更改,现在无法加载视频。如果我允许在 ProgressEventOutputHandler 函数中创建新的 ByteArray,视频会再次加载。
短版:
试试我粘贴在这里的代码:Github Snippet link
长版:
这个有点长但希望它能一劳永逸...不要担心砖墙的事情,墙壁是用来砸的。为了保持灵感,请查看 VC:One 实验室使用 appendBytes
的一些内部演示:
- MP4 Seeking Experiment : 研究
appendBytes
帧数据访问和 time/seek 处理。实时帧字节仅使用 AS3 代码从 MP4 格式转换为 FLV 格式。
- Speed Adjust of Audio & Video : 用于视频分离和效果实验中的实时 MP3 音频。需要 MP4/FLV 音轨中包含 MP3 数据的文件。
- Synchronised Video Frames : 对于多个视频显示相同的帧数。
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 解码器配置 字节,第一个音频标签包含 音频特定配置 字节。此数据不是实际的媒体帧,而是像 audio/video 标签一样简单地打包。这些是 MPEG 播放所必需的。可以在 MP4 容器内的
STSD
元数据条目(MOOV
原子)中找到相同的字节。现在下一个找到的视频标签将(或应该)是视频的实际第一帧。
- 视频关键帧:从0x09开始,下一个第11个字节是0x17,第12个字节是0x01
- Audio TAG AAC : 从 0x08 开始,下一个第 11 个字节是 0xAF & 第 12 个字节是 0x01
- Audio TAG MP3 : 从 0x08 开始,接下来的第 11 个字节是 0x2F & 第 12 个字节是 0xFF
1) 字节复制和检查值:
您正在寻找代表视频的字节 "tag"。除了元数据标记之外,您现在可以期望 "tag" 表示音频或视频帧的容器。有两种方法可以将标记字节放入 "temporary byte array"(我们将其命名为 temp_BA
)。
ReadBytes
(慢):提取 source_BA
中 start/end 范围内的各个字节值
WriteBytes
(快速):即时复制 source_BA
中的 start/end 字节范围
Readbytes 解释:告诉源将其字节读入目标。源将向前读取到从其当前偏移量(位置)开始的长度。 在继续阅读之前转到正确的源位置...
source_BA.readBytes( into Target_BA, Pos within Target_BA, length of bytes required );
执行上述行后,源位置现在已经向前移动以说明新的行进长度。 (公式:Source new Pos = previousPos + BytesLengthRequired)。
Writebytes 解释:告诉目标从源复制一个字节范围。自从从已知信息(来自源)复制以来速度很快。 目标从当前位置开始写入...
target_BA.writeBytes( from source_BA, Pos within source_BA, length of bytes required );
以上行执行后,注意Source和Target的位置都没有变化.
使用上述方法从特定 source_BA.position = x
获取所需的标记字节到 temp_BA
。
要检查任何字节(它的值),使用下面的方法更新一些 int
类型的变量:
- 读取 one-byte value : 使用
my_Integer = source_BA.readByte();
- 读取 two-byte value : 使用
my_Integer = source_BA.readUnsignedShort();
- 读取 four-byte value : 使用
my_Integer = source_BA.readUnsignedInt();
- 变量
Number
用于 eight-byte value :使用 my_Number = source_BA.readDouble();
注意 :不要混淆 .readByte();
提取数值(字节)与类似的 .readBytes()
复制字节块到另一个字节数组。
2) 查找视频关键帧(或 I 帧):
[带关键帧的视频 TAG 插图图像 H264/AAC]
查找视频关键帧
- 从起始偏移量开始,使用
while
循环现在 [向前] 遍历字节,搜索每个字节的 one-byte value of "9 “(十六进制:0x09
),当找到时,我们进一步检查前面的字节以确认它确实是一个真正的关键帧,而不仅仅是随机出现的“9”。
- 在 H.264 视频编解码器的情况下,在正确的“9”字节位置 (xPos),我们期望 第 11 和第 12 字节总是在前面分别为“17”和“01”。
If
即 == true
然后我们检查三个 Tag Size 字节并向其添加 15预期从 Source 写入 Target 的字节总长度的整数 (temp_BA
)。我们添加了 15 来说明 before 的 11 个字节以及 after 预期的 4 个字节 TAG DATA .标签结尾的这 4 个字节是 "Previous Tag Size",这个数量实际上包括前面的 11 个字节,但不包括这些结尾的 4 个字节本身。
- 我们告诉
temp_BA
写入 Source 的 bytes(你的 videoByteArray
)从“9”字节 (xPos) 的 位置开始,长度为 "Tag Size" + 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 )
。当计时器 运行s 到达每个 FPS 秒片时,它将 运行 append_PLAY();
函数依次 运行s a get_frame_Tag();
功能。
NS.seek(0)
: 将 NetStream 放入 "seek mode"。 (数字无关紧要,但必须存在于命令中)。任何 "ahead frames" 缓冲区都被清除,它们将不会有(图像)帧更新,直到..
RESET_SEEK
:结束 "seek mode",现在允许图像更新。使用 RESET_SEEK
命令后附加的第一个标签必须是带有视频关键帧的标签。 (对于纯音频,这可以是任何标签,因为从技术上讲,所有音频标签都是音频关键帧)
END_SEQUENCE
:(对于 MPEG H.264)播放所有剩余的 "ahead frames"(清空缓冲区)。一旦耗尽,您现在可以附加任何类型的视频标签。请记住 H.264 需要向前移动的时间戳,如果您看到该死的像素,那么您的下一个标记时间戳是错误的(太高或太低)。如果您只附加一帧(海报图片?),您可以使用 END_SEQUEMCE
来耗尽缓冲区并显示该帧(无需等待缓冲区先填充 x 帧数量)...
play 函数 充当中间人函数来管理事物,而不会使 get frame 函数与 If
混淆语句等。管理事物意味着例如检查是否下载了足够的字节以根据标签大小甚至开始获取帧。
4) 工作示例的源代码:
代码太长。请参阅下面的 link:
https://gist.github.com/Valerio-Charles-VC1/657054b773dba9ba1cbc
希望对您有所帮助。 VC
我在 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);
.
编辑:
为了解决这个问题,我按照 VC.One 的说明进行了操作:
将
videoByteArray = new ByteArray();
移动到我的初始化函数,并在此函数中创建了tempVideoByteArray = new ByteArray();
。更新我的 ProgressEventOutputHandler 函数,使其不再为 videoByteArray 创建新的 ByteArray 并更改此行 -
nativeProcess.standardOutput.readBytes(videoByteArray, videoByteArray.length, nativeProcess.standardOutput.bytesAvailable);
我没有做任何更改,现在无法加载视频。如果我允许在 ProgressEventOutputHandler 函数中创建新的 ByteArray,视频会再次加载。
短版:
试试我粘贴在这里的代码:Github Snippet link
长版:
这个有点长但希望它能一劳永逸...不要担心砖墙的事情,墙壁是用来砸的。为了保持灵感,请查看 VC:One 实验室使用 appendBytes
的一些内部演示:
- MP4 Seeking Experiment : 研究
appendBytes
帧数据访问和 time/seek 处理。实时帧字节仅使用 AS3 代码从 MP4 格式转换为 FLV 格式。 - Speed Adjust of Audio & Video : 用于视频分离和效果实验中的实时 MP3 音频。需要 MP4/FLV 音轨中包含 MP3 数据的文件。
- Synchronised Video Frames : 对于多个视频显示相同的帧数。
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 解码器配置 字节,第一个音频标签包含 音频特定配置 字节。此数据不是实际的媒体帧,而是像 audio/video 标签一样简单地打包。这些是 MPEG 播放所必需的。可以在 MP4 容器内的
STSD
元数据条目(MOOV
原子)中找到相同的字节。现在下一个找到的视频标签将(或应该)是视频的实际第一帧。 - 视频关键帧:从0x09开始,下一个第11个字节是0x17,第12个字节是0x01
- Audio TAG AAC : 从 0x08 开始,下一个第 11 个字节是 0xAF & 第 12 个字节是 0x01
- Audio TAG MP3 : 从 0x08 开始,接下来的第 11 个字节是 0x2F & 第 12 个字节是 0xFF
1) 字节复制和检查值:
您正在寻找代表视频的字节 "tag"。除了元数据标记之外,您现在可以期望 "tag" 表示音频或视频帧的容器。有两种方法可以将标记字节放入 "temporary byte array"(我们将其命名为 temp_BA
)。
ReadBytes
(慢):提取source_BA
中 start/end 范围内的各个字节值
WriteBytes
(快速):即时复制source_BA
中的 start/end 字节范围
Readbytes 解释:告诉源将其字节读入目标。源将向前读取到从其当前偏移量(位置)开始的长度。 在继续阅读之前转到正确的源位置...
source_BA.readBytes( into Target_BA, Pos within Target_BA, length of bytes required );
执行上述行后,源位置现在已经向前移动以说明新的行进长度。 (公式:Source new Pos = previousPos + BytesLengthRequired)。
Writebytes 解释:告诉目标从源复制一个字节范围。自从从已知信息(来自源)复制以来速度很快。 目标从当前位置开始写入...
target_BA.writeBytes( from source_BA, Pos within source_BA, length of bytes required );
以上行执行后,注意Source和Target的位置都没有变化.
使用上述方法从特定 source_BA.position = x
获取所需的标记字节到 temp_BA
。
要检查任何字节(它的值),使用下面的方法更新一些 int
类型的变量:
- 读取 one-byte value : 使用
my_Integer = source_BA.readByte();
- 读取 two-byte value : 使用
my_Integer = source_BA.readUnsignedShort();
- 读取 four-byte value : 使用
my_Integer = source_BA.readUnsignedInt();
- 变量
Number
用于 eight-byte value :使用my_Number = source_BA.readDouble();
注意 :不要混淆 .readByte();
提取数值(字节)与类似的 .readBytes()
复制字节块到另一个字节数组。
2) 查找视频关键帧(或 I 帧):
[带关键帧的视频 TAG 插图图像 H264/AAC]
查找视频关键帧
- 从起始偏移量开始,使用
while
循环现在 [向前] 遍历字节,搜索每个字节的 one-byte value of "9 “(十六进制:0x09
),当找到时,我们进一步检查前面的字节以确认它确实是一个真正的关键帧,而不仅仅是随机出现的“9”。 - 在 H.264 视频编解码器的情况下,在正确的“9”字节位置 (xPos),我们期望 第 11 和第 12 字节总是在前面分别为“17”和“01”。
If
即== true
然后我们检查三个 Tag Size 字节并向其添加 15预期从 Source 写入 Target 的字节总长度的整数 (temp_BA
)。我们添加了 15 来说明 before 的 11 个字节以及 after 预期的 4 个字节 TAG DATA .标签结尾的这 4 个字节是 "Previous Tag Size",这个数量实际上包括前面的 11 个字节,但不包括这些结尾的 4 个字节本身。- 我们告诉
temp_BA
写入 Source 的 bytes(你的videoByteArray
)从“9”字节 (xPos) 的 位置开始,长度为 "Tag Size" + 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 )
。当计时器 运行s 到达每个 FPS 秒片时,它将 运行 append_PLAY();
函数依次 运行s a get_frame_Tag();
功能。
NS.seek(0)
: 将 NetStream 放入 "seek mode"。 (数字无关紧要,但必须存在于命令中)。任何 "ahead frames" 缓冲区都被清除,它们将不会有(图像)帧更新,直到..RESET_SEEK
:结束 "seek mode",现在允许图像更新。使用RESET_SEEK
命令后附加的第一个标签必须是带有视频关键帧的标签。 (对于纯音频,这可以是任何标签,因为从技术上讲,所有音频标签都是音频关键帧)END_SEQUENCE
:(对于 MPEG H.264)播放所有剩余的 "ahead frames"(清空缓冲区)。一旦耗尽,您现在可以附加任何类型的视频标签。请记住 H.264 需要向前移动的时间戳,如果您看到该死的像素,那么您的下一个标记时间戳是错误的(太高或太低)。如果您只附加一帧(海报图片?),您可以使用END_SEQUEMCE
来耗尽缓冲区并显示该帧(无需等待缓冲区先填充 x 帧数量)...
play 函数 充当中间人函数来管理事物,而不会使 get frame 函数与 If
混淆语句等。管理事物意味着例如检查是否下载了足够的字节以根据标签大小甚至开始获取帧。
4) 工作示例的源代码:
代码太长。请参阅下面的 link: https://gist.github.com/Valerio-Charles-VC1/657054b773dba9ba1cbc
希望对您有所帮助。 VC