如何覆盖视频的 i 帧?

发布于 2024-08-28 08:47:12 字数 93 浏览 4 评论 0原文

我想销毁视频的所有 i 帧。这样做我想检查仅加密视频的 i 帧是否足以使其无法观看。我该怎么做?仅删除它们并重新压缩视频与真正覆盖流中的 i 帧而不重新计算 b 帧等不同。

I want to destroy all i-frames of a video. Doing this I want to check if encrypting only the i-frames of a video would be sufficient for making it unwatchable. How can I do this? Only removing them and recompressing the video would not be the same as really overwriting the i-frame in the stream without recalculating b-frames etc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

计㈡愣 2024-09-04 08:47:12

使用 libavformat(来自 ffmpeg 的库),您可以将视频解复用为代表单个帧的数据包。然后,您可以对标记为关键帧的数据包中的数据进行加密。最后,您可以将视频重新复用为新文件。 这里有一个很好的 libavformat/libavcodec 教程。您不必实际解码/编码帧,因为我假设您只想加密压缩数据。在这种情况下,一旦读取了 AVPacket,如果它是关键帧(packet->flags & PKT_FLAG_KEY),只需加密其数据即可。然后您必须将数据包写入新文件。

需要注意的一件事是,当您只加密从 libavformat 或其他一些解复用软件返回的 I 帧数据包时,您可能必须小心,因为它们可能包含来自存储在比特流中的其他标头的数据。例如,我经常看到 libavformat 返回序列或一组图片标头作为视频帧数据包的一部分。销毁此信息可能会使您的测试无效。

解决该问题的一种可能更简单的方法是研究用于对视频进行编码的编解码器的比特流语法,并使用起始码来确定帧从哪里开始以及它们是否是 I 帧。一个问题是,大多数视频文件都有一个围绕实际压缩数据的容器(AVI、MP4、MPEG-PS/TS),您不希望加密该区域中的任何内容。您很可能会发现属于散布在单个帧的压缩数据中的容器格式的标头信息。因此,您可以从命令行使用 ffmpeg 来仅输出原始压缩视频数据:

ffmpeg -i filename -an -vcodec copy -f rawvideo output_filename

这将创建一个仅包含视频数据(无音频)且没有容器的文件。从这里您可以使用特定视频格式的起始代码来查找文件中与 I 帧相对应的字节范围。

例如,在 MPEG-4 中,您将查找 32 位起始码 0x000001b6 来指示 VOP(视频对象平面)的开始。通过检测起始码后面的两位是否等于00来判断是否为I帧。如果是 I 帧,则加密数据,直到到达下一个起始代码(24 位 0x000001)。您可能希望保持起始代码和帧类型代码不变,以便稍后可以知道从哪里开始解密。

关于加密 I 帧是否会使视频无法观看的测试结果;这取决于你对“不可观看”的理解。我希望您能够辨认出原始视频中存在的主要形状(如果它在运动),因为它的信息必须编码在 B 或 P 帧中,但颜色和细节仍然是垃圾。我发现 I 帧中的一个比特错误会使整个图片组(I 帧和依赖于它的所有帧)看起来像垃圾。压缩的目的是减少冗余,使每个比特都至关重要。摧毁整个 I 帧几乎肯定会使其无法观看。

编辑:对评论的响应

起始代码保证是字节对齐的,因此您可以一次将文件一个字节读入 4 字节缓冲区并测试它是否等于起始代码。在 C++ 中,您可以通过以下方式完成此操作:

#include <iostream>
using namespace std;
//...

//...
ifstream ifs("filename", ios::in | ios::binary);
//initialize buffer to 0xffffffff
unsigned char buffer[4] = {0xff, 0xff, 0xff, 0xff};
while(!ifs.eof())
{
    //Shift to make space for new read.
    buffer[0] = buffer[1];
    buffer[1] = buffer[2];
    buffer[2] = buffer[3];

    //read next byte from file
    buffer[3] = ifs.get();

    //see if the current buffer contains the start code.
    if(buffer[0]==0x00 && buffer[1]==0x00 && buffer[2]==0x01 && buffer[3]==0xb6)
    {
        //vop start code found
        //Test for I-frame
        unsigned char ch = ifs.get();
        int vop_coding_type = (ch & 0xc0) >> 6;   //masks out the first 2 bits and shifts them to the least significant bits of the uchar
        if(vop_coding_type == 0)
        {
            //It is an I-frame
            //...
        }
    }
}

查找 24 位起始代码类似,只需使用 3 字节缓冲区。请记住,在执行此操作之前,您必须使用 ffmpeg 删除视频容器,否则可能会破坏一些容器信息。

Using libavformat (library from ffmpeg), you can demultiplex the video into packets that represent a single frame. You can then encrypt data in the packets that are marked as key frames. Finally you can remultiplex the video into a new file. There is a good libavformat/libavcodec tutorial here. You will not have to actually decode/encode the frames because I assume you just want to encrypt the compressed data. In this case, once you read the AVPacket, just encrypt its data if it's a key frame (packet->flags & PKT_FLAG_KEY). You would then have to write the packets to a new file.

One thing to note is that you might have to be careful when you just encrypt the I-frame packets returned from libavformat or some other demuxing software since they may include data from other headers that are stored in the bitstream. For instance, I have often seen libavformat return sequence or group of picture headers as part of a video frame packet. Destroying this information may invalidate your test.

A possibly easier way to approach the problem would be to research the bitstream syntax of the codec used to encode the video and use the start codes to determine where frames start and whether or not they are I-frames. One problem is that most video files have a container (AVI, MP4, MPEG-PS/TS) around the actual compressed data and you would not want to encrypt anything in that area. You will most likely find header information belonging to the container format interspersed within the compressed data of a single frame. So you could use ffmpeg from the command line to output just the raw compressed video data:

ffmpeg -i filename -an -vcodec copy -f rawvideo output_filename

This will create a file with only the video data(no audio) with no container. From here you can use the start codes of the specific video format to find the ranges of bytes in the file that correspond to I-frames.

For instance, in MPEG-4, you would be looking for the 32-bit start code 0x000001b6 to indicate the start of a VOP (video object plane). You could determine whether it is an I-frame or not by testing whether two bits immediately following the start code are equal to 00. If it is an I frame, encrypt the data until you reach the next start code (24-bit 0x000001). You'll probably want to leave the start code and frame type code untouched so you can tell later where to start decrypting.

Concerning outcome of your test as to whether or not encrypting I-frames will make a video unwatchable; it depends on your meaning of unwatchable. I would expect that you may be able to make out a major shape that existed in the original video if it is in motion since its information would have to be encoded in the B or P frames, but the color and detail would still be garbage. I have seen a single bit error in an I-frame make the entire group of pictures (the I-frame and all frames that depend on it) look like garbage. The purpose of the compression is to reduce redundancy to the point that each bit is vital. Destroying the entire I-frame will almost definitely make it unwatchable.

Edit: Response to comment

Start codes are guaranteed to be byte-aligned, so you can read the file a byte at a time into a 4 byte buffer and test whether it is equal to the start code. In C++, you can do this by the following:

#include <iostream>
using namespace std;
//...

//...
ifstream ifs("filename", ios::in | ios::binary);
//initialize buffer to 0xffffffff
unsigned char buffer[4] = {0xff, 0xff, 0xff, 0xff};
while(!ifs.eof())
{
    //Shift to make space for new read.
    buffer[0] = buffer[1];
    buffer[1] = buffer[2];
    buffer[2] = buffer[3];

    //read next byte from file
    buffer[3] = ifs.get();

    //see if the current buffer contains the start code.
    if(buffer[0]==0x00 && buffer[1]==0x00 && buffer[2]==0x01 && buffer[3]==0xb6)
    {
        //vop start code found
        //Test for I-frame
        unsigned char ch = ifs.get();
        int vop_coding_type = (ch & 0xc0) >> 6;   //masks out the first 2 bits and shifts them to the least significant bits of the uchar
        if(vop_coding_type == 0)
        {
            //It is an I-frame
            //...
        }
    }
}

Finding a 24-bit start code is similar, just use a 3 byte buffer. Remember that you must remove the video container with ffmpeg before doing this or you may destroy some of the container information.

平生欢 2024-09-04 08:47:12

On Windows you could copy file without recompress using VFW and skip I-frames. To find I-frames you could use FindSample function with FIND_KEY flag.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文