65.9K
CodeProject 正在变化。 阅读更多。
Home

AviFile 库的简单 C# 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (203投票s)

2004 年 6 月 13 日

CPOL

9分钟阅读

viewsIcon

2201606

downloadIcon

72156

在 .NET 中编辑 AVI 文件。

引言

这篇文章是为那些不想花费数小时处理 AVIFile 函数,而只是想读取或修改简单 AVI 视频的任何人准备的。我已将最重要的一些 AVIFile 函数封装到三个易于使用的 C# 类中,它们可以处理以下任务:

  • 从视频流读取图像。
  • 解压缩一个压缩的视频流。
  • 压缩一个未压缩的视频流。
  • 更改视频流的压缩格式。
  • 将视频流导出到单独的.avi 文件。
  • 将音频流导出到.wav 文件。
  • 将音频和视频流的几秒钟复制到一个新的.avi 文件。
  • .wav 文件向视频添加声音。
  • 从位图列表创建一个新的视频流。
  • 向现有视频流(已压缩或未压缩)添加帧。
  • 将帧插入到流中。
  • 从流中复制或删除帧。

这些功能涵盖了常见的用例,例如从几张图片和一个 wave 音频创建视频、从视频中提取音轨、剪切短片段或从电影中抓取单张图片。

本文包含两个部分:

  • 首先,我将解释演示应用程序,以便您可以使用它来自己探索该库。
  • 然后,我将逐步解释库的工作原理。

如何使用该库 - 演示应用程序的详细介绍

“探索”选项卡

探索”选项卡允许您探索 AVI 文件,并提供不需要可编辑流的简单任务。

在表单的顶部,您可以选择要探索的 AVI 文件。text.avi(来自测试数据文件夹)已预先选中。在左侧,您可以显示有关视频和 wave 音频流(如果可用)的标题信息。此外,您还可以在 PictureBox 中看到视频的图像帧。

图像由 VideoStream 对象读取。GetFrameOpen 准备流以解压缩帧,GetFrameClose 释放用于解压缩帧的资源,而 GetBitmap 则解压缩帧并将其转换为 System.Drawing.Bitmap

AviManager aviManager = new AviManager(txtAviFileName.Text, true);
VideoStream aviStream = aviManager.GetVideoStream();
aviStream.GetFrameOpen();
picFrame.Image = 
  aviStream.GetBitmap(Convert.ToInt32(numPosition.Value));
aviStream.GetFrameClose();
aviManager.Close();

在演示表单的中间,您可以处理整个流。

解压缩会移除视频流的压缩。它会创建一个新的文件和视频流,从旧流中解压缩每个帧,并将其写入新流。结果是一个大的新.avi 文件,具有相同的视频但没有压缩。

压缩会更改视频的压缩格式,或对未压缩视频应用压缩。它的功能与解压缩相同,但会压缩新流。这两个函数使用同一个方法 CopyFile

private void CopyFile(String newName, bool compress){
    //open compressed file
    AviManager aviManager = 
        new AviManager(txtAviFileName.Text, true);
    VideoStream aviStream = aviManager.GetVideoStream();
    //create un-/re-compressed file
    AviManager newManager = 
        aviStream.DecompressToNewFile(newName, compress);

    //close compressed file
    aviManager.Close();
    //save and close un-/re-compressed file
    newManager.Close();
}

每当 VideoStream 实例创建压缩流时,它会询问您有关编解码器和设置的信息。

提取位图会将视频分割成许多单独的位图文件。

VideoStream stream = aviManager.GetVideoStream();
stream.GetFrameOpen();

String path = @"..\..\testdata\";
for(int n=0; n<stream.CountFrames; n++){
    stream.ExportBitmap(n, path+n.ToString()+".bmp");
}

stream.GetFrameClose();

当然,您可以将图像保存为任何格式。ExportBitmap 只是这三行代码的快捷方式。

Bitmap bmp = stream.GetBitmap(position);
bmp.Save(FileName);
bmp.Dispose();

提取视频会将整个视频流复制到一个新的 AVI 文件中。您可以使用它来删除所有其他流,如 MIDI、文本和 Wave 音频。

下面的框处理 Wave 音频。提取音频会将整个音频流复制到一个 Wave 文件中。这不是一项艰巨的任务,只需要四行代码。

AviManager aviManager = 
    new AviManager(txtAviFileName.Text, true);
AudioStream audioStream = aviManager.GetWaveStream();
audioStream.ExportStream( @"..\..\testdata\sound.wav" );
aviManager.Close();

提取几秒钟允许您在 X 秒和 Y 秒之间复制视频和音频。首先,一个 CopyForm 对话框让您输入 X 和 Y,然后这些部分将从视频和音频流中剪切出来。

AviManager aviManager = new AviManager(txtAviFileName.Text, true);
VideoStream aviStream = aviManager.GetVideoStream();
CopyForm dialog = new CopyForm(0,
  aviStream.CountFrames / aviStream.FrameRate);
if (dialog.ShowDialog() == DialogResult.OK) {
  int startSecond = dialog.Start;
  int stopSecond = dialog.Stop;

  AviManager newFile = aviManager.CopyTo(
    "..\\..\\testdata\\video.avi", startSecond, stopSecond);
  newFile.Close();
}
aviManager.Close();

添加音频允许您选择一个.wav 文件,并将其添加到视频中。例如,您可以使用此功能为静音视频添加音轨,或为通过提取视频提取的视频重新添加声音。添加音频是一项简单的任务,只需要三行代码。

String fileName = GetFileName("Sounds (*.wav)|*.wav");
if(fileName != null){
    AviManager aviManager = 
        new AviManager(txtAviFileName.Text, true);
    aviManager.AddAudioStream(fileName);
    aviManager.Close();
}

最后一组功能是关于创建新的视频流。在框中输入图像文件列表并进行动画处理。

创建未压缩会从图像构建一个新视频,并将其保存而不进行任何压缩。创建并压缩执行相同操作,但它会显示压缩设置对话框并压缩图像。这两种方法都会创建一个新文件,并将一个示例位图传递给 AddVideoStream。示例位图用于设置新流的格式。然后,列表中的所有图像都会被添加到视频中。

//load the first image
Bitmap bitmap = (Bitmap)Image.FromFile(txtFileNames.Lines[0]);
//create a new AVI file
AviManager aviManager = 
    new AviManager(@"..\..\testdata\new.avi", false);
//add a new video stream and one frame to the new file
VideoStream aviStream = 
    aviManager.AddVideoStream(true, 2, bitmap);

Bitmap bitmap;
int count = 0;
for(int n=1; n<txtFileNames.Lines.Length; n++){
    if(txtFileNames.Lines[n].Trim().Length > 0){
        bitmap = 
           (Bitmap)Bitmap.FromFile(txtFileNames.Lines[n]);
        aviStream.AddFrame(bitmap);
        bitmap.Dispose();
        count++;
    }
}
aviManager.Close();

添加帧会将图像追加到现有视频流。对于未压缩的视频流,我们可以通过简单地打开流并像平常一样添加帧来追加帧。但是,已压缩的流无法重新压缩。AVIStreamWrite(由 AddFrame 使用)不会返回任何错误;但无论如何,它都会以未压缩的方式添加新帧,并产生奇怪的彩色像素风暴。要将帧添加到已压缩的流,必须先解压缩现有帧,然后将其添加到新的已压缩流中。然后才能将其他帧添加到该流中。

//open file
Bitmap bmp = (Bitmap)Image.FromFile(txtFileNames.Lines[0]);
AviManager aviManager = 
    new AviManager(txtAviFileName.Text, true);
VideoStream aviStream = aviManager.GetVideoStream();

//streams cannot be edited - copy to a new file
AviManager newManager = aviStream.DecompressToNewFile(
                        @"..\..\testdata\temp.avi", true);
aviStream = newManager.GetOpenStream(0);

//add images
Bitmap bitmap;
for(int n=0; n<txtFileNames.Lines.Length; n++){
    if(txtFileNames.Lines[n].Trim().Length > 0){
        bitmap = 
            (Bitmap)Bitmap.FromFile(txtFileNames.Lines[n]);
        aviStream.AddFrame(bitmap);
        bitmap.Dispose();
    }
}

aviManager.Close(); //close old file
newManager.Close(); //save and close new file

//delete old file, replace with new file
System.IO.File.Delete(txtAviFileName.Text);
System.IO.File.Move(@"..\..\testdata\temp.avi", 
                            txtAviFileName.Text);

现在您已经了解了 AVIFile 包装类如何使用,让我们看一下后台。

“编辑”选项卡

编辑”选项卡演示了可编辑 AVI 流的任务,例如在流的任何位置粘贴帧或更改帧率。

选择文件进行编辑后,将从视频流创建一个可编辑流,并且编辑器按钮将被启用。正常的视频流是锁定的;要插入和删除帧,需要一个可编辑流。

AviManager file = new AviManager(fileName, true);
VideoStream stream = file.GetVideoStream();
EditableVideoStream editableStream = 
           new EditableVideoStream(stream);
file.Close();

在左侧,您可以复制或剪切帧序列,并将它们粘贴到同一流中的其他位置。

将帧从一个流复制到另一个流或同一流中,只需要两行代码。

//copy frames
IntPtr copiedData = editableStream.Copy(start, length);

//insert frames
editableStream.Paste(copiedData, 0, position, length);

删除帧没有其他方法,只能剪切并丢弃它们。

//cut and paste frames
IntPtr copiedData = editableStream.Cut(start, length);
editableStream.Paste(copiedData, 0, position, length);

//delete frames == cut without paste
IntPtr deletedData = editableStream.Cut(start, length);

在对话框的中间,您可以将图像文件中的帧插入到流中的任何位置,并更改帧率以使视频播放速度变慢或变快。

我们只能粘贴流,不能粘贴位图,因此列表中的位图会写入一个临时 AVI 文件,然后作为流粘贴。

//create temporary video file
String tempFileName = System.IO.Path.GetTempFileName() + ".avi";
AviManager tempFile = new AviManager(tempFileName, false);

//write the new frames into the temporary video stream
Bitmap bitmap = 
  (Bitmap)Image.FromFile(txtNewFrameFileName.Lines[0].Trim());
tempFile.AddVideoStream(false, 1, bitmap);
VideoStream stream = tempFile.GetVideoStream();

for (int n=1; n<txtNewFrameFileName.Lines.Length; n++) {
   if (txtNewFrameFileName.Lines[n].Trim().Length > 0) {
       stream.AddFrame(
         (Bitmap)Image.FromFile(txtNewFrameFileName.Lines[n]));
   }
}

//paste the video into the editable stream
editableStream.Paste(stream, 0,
   (int)numPastePositionBitmap.Value, stream.CountFrames);

您的视频是否太慢或太快?告诉播放器应用程序每秒播放更多/更少帧。

Avi.AVISTREAMINFO info = editableStream.StreamInfo;
info.dwRate = (int)(numFrameRate.Value * 10000);
info.dwScale = 10000;
editableStream.SetInfo(info);

最后一个框不是用于编辑的,它只是一个预览播放器。在将可编辑流保存到 AVI 文件之前,您应该预览它。

实现预览播放器很容易,您只需要一个 PictureBox 和要播放的视频流。一个显示当前帧索引的标签也很有用。一个开始按钮,一个停止按钮,然后就可以了。

private void btnPlay_Click(object sender, EventArgs e) {
    player = new AviPlayer(editableStream, 
                pictureboxPreview, labelFrameIndex);
    player.Stopped += new System.EventHandler(player_Stopped);
    player.Start();
    SetPreviewButtonsState();
}

private void player_Stopped(object sender, EventArgs e) {
        btnPlay.Invoke(
           new SimpleDelegate(SetPreviewButtonsState));
}

private void SetPreviewButtonsState() {
        btnPlay.Enabled = ! player.IsRunning;
        btnStop.Enabled = player.IsRunning;
}

private void btnStop_Click(object sender, EventArgs e) {
        player.Stop();
}

工作原理

AviManger 管理 AVI 文件中的流。构造函数接受文件名并打开它。Close 关闭所有打开的流和文件本身。您可以使用 AddVideoStreamAddAudioStream 添加新流。新的视频流是空的,Wave 流只能从 Wave 文件创建。在创建了一个空的视频流之后,使用 VideoStream 的方法来填充它。但是,当您添加流时,实际上发生了什么?

创建视频流

有两种方法可以创建新的视频流:从示例位图创建,或从显式格式信息创建。这两种方法都做同样的事情,它们将参数传递给 VideoStream,并将新流添加到内部打开的流列表中,以便在关闭文件之前关闭它们。

public VideoStream AddVideoStream(
    bool isCompressed, //display the compression dialog,
     // create a compressed stream
    int frameRate, //frames per second
    int frameSize, //size of one frame in bytes
    int width, int height, PixelFormat format //format of 
                                              //the bitmaps
    ){

    VideoStream stream = new VideoStream(
        aviFile,
        isCompressed,
        frameRate,
        frameSize,
        width, height, format);

    streams.Add(stream);
    return stream;
}

public VideoStream AddVideoStream(
    bool isCompressed, //display the compression dialog,
      // create a compressed stream
    int frameRate, //frames per second
    Bitmap firstFrame //get the format from this image
      // and add it to the new stream
    ){

    VideoStream stream = new VideoStream(
        aviFile,
        isCompressed,
        frameRate,
        firstFrame);

    streams.Add(stream);
    return stream;
}

然后,VideoStream 使用格式数据来创建新流。它调用 AVIFileCreateStream,如果 writeCompressed 指示,则调用 AVIMakeCompressedStream

public VideoStream(
    int aviFile, //pointer to the file object
    bool writeCompressed, //true: create compressed stream
    int frameRate, //frames per second
    ...
    ){

    //store format information
    //...

    //create the stream
    CreateStream();
}

private void CreateStream(){
    //fill stream information
    Avi.AVISTREAMINFO strhdr = new Avi.AVISTREAMINFO();
    strhdr.fccType = Avi.mmioStringToFOURCC("vids", 0);
    strhdr.fccHandler = Avi.mmioStringToFOURCC("CVID", 0);
    strhdr.dwScale = 1;
    strhdr.dwRate = frameRate;
    strhdr.dwSuggestedBufferSize = frameSize;
    strhdr.dwQuality = -1; //default
    strhdr.rcFrame.bottom = (uint)height;
    strhdr.rcFrame.right = (uint)width;
    strhdr.szName = new UInt16[64];

    //create the stream
    int result = Avi.AVIFileCreateStream(aviFile, 
                          out aviStream, ref strhdr);

    if(writeCompressed){
        //create a compressed stream from 
        //the uncompressed stream
        CreateCompressedStream();
    }
}

private void CreateCompressedStream(){
    Avi.AVICOMPRESSOPTIONS_CLASS options = 
             new Avi.AVICOMPRESSOPTIONS_CLASS();
    options.fccType = (uint)Avi.streamtypeVIDEO;
    options.lpParms = IntPtr.Zero;
    options.lpFormat = IntPtr.Zero;

    //display the compression options dialog
    Avi.AVISaveOptions(
      IntPtr.Zero,
      Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE,
      1, ref aviStream, ref options);

    //get a compressed stream
    Avi.AVICOMPRESSOPTIONS structOptions = 
                            options.ToStruct();
    int result = Avi.AVIMakeCompressedStream(
        out compressedStream,
        aviStream,
        ref structOptions, 0);

    //format the compressed stream
    SetFormat(compressedStream);
}

AVICOMPRESSOPTIONS_CLASSAVICOMPRESSOPTIONS 结构的一个类表示。使用类而不是结构是处理指向指针的最简单方法。如果您不知道我在说什么,您可能从未在 .NET 中使用过 AVISaveOptionsAVISaveV。看一下原始声明:

BOOL AVISaveOptions(
  HWND hwnd,
  UINT uiFlags,
  int nStreams,
  PAVISTREAM * ppavi,
  LPAVICOMPRESSOPTIONS * plpOptions
);

LPAVICOMPRESSOPTIONS 是指向 AVICOMPRESSOPTIONS 结构指针的指针。在 C# 中,结构是按值传递的。如果您通过 ref 传递结构,则会传递指向该结构的指针。类的实例始终作为指针传递给方法。因此,按 ref 传递的类参数意味着指向对象指针的指针。AVISaveOptionsAVICOMPRESSOPTIONS 的 C# 声明是:

[DllImport("avifil32.dll")]
public static extern bool AVISaveOptions(
    IntPtr hwnd,
    UInt32 uiFlags,
    Int32 nStreams,
    ref IntPtr ppavi,
    ref AVICOMPRESSOPTIONS_CLASS plpOptions
    );

[StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct AVICOMPRESSOPTIONS {
    public UInt32   fccType;
    public UInt32   fccHandler;
    public UInt32   dwKeyFrameEvery;
    public UInt32   dwQuality;
    public UInt32   dwBytesPerSecond;
    public UInt32   dwFlags;
    public IntPtr   lpFormat;
    public UInt32   cbFormat;
    public IntPtr   lpParms;
    public UInt32   cbParms;
    public UInt32   dwInterleaveEvery;
}

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class AVICOMPRESSOPTIONS_CLASS {
    public UInt32   fccType;
    public UInt32   fccHandler;
    public UInt32   dwKeyFrameEvery;
    public UInt32   dwQuality;
    public UInt32   dwBytesPerSecond;
    public UInt32   dwFlags;
    public IntPtr   lpFormat;
    public UInt32   cbFormat;
    public IntPtr   lpParms;
    public UInt32   cbParms;
    public UInt32   dwInterleaveEvery;

    public AVICOMPRESSOPTIONS ToStruct(){
        AVICOMPRESSOPTIONS returnVar = new AVICOMPRESSOPTIONS();
        returnVar.fccType = this.fccType;
        returnVar.fccHandler = this.fccHandler;
        returnVar.dwKeyFrameEvery = this.dwKeyFrameEvery;
        returnVar.dwQuality = this.dwQuality;
        returnVar.dwBytesPerSecond = this.dwBytesPerSecond;
        returnVar.dwFlags = this.dwFlags;
        returnVar.lpFormat = this.lpFormat;
        returnVar.cbFormat = this.cbFormat;
        returnVar.lpParms = this.lpParms;
        returnVar.cbParms = this.cbParms;
        returnVar.dwInterleaveEvery = this.dwInterleaveEvery;
        return returnVar;
    }
}

通过这个变通方法,我们能够在 C# 中调用 AVISaveOptions 和(稍后)AVISaveV。现在,可以使用 AddFrame 用图像帧填充新流: 

public void AddFrame(Bitmap bmp){
    bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);

    if (countFrames == 0){
       //  the format of the first frame defines the format of the stream
       CopyPalette(bmp.Palette);
       SetFormat(writeCompressed ? compressedStream : aviStream,
                 countFrames);
    }

    //lock the memory block
    BitmapData bmpDat = bmp.LockBits(
        new Rectangle(
        0,0, bmp.Width, bmp.Height),
        ImageLockMode.ReadOnly, bmp.PixelFormat);

    //add the bitmap to the (un-)compressed stream
    int result = Avi.AVIStreamWrite(
        writeCompressed ? compressedStream : aviStream,
        countFrames, 1,
        bmpDat.Scan0,
        (Int32)(bmpDat.Stride * bmpDat.Height),
        0, 0, 0);

    //unlock the memory block
    bmp.UnlockBits(bmpDat);

    //count the frames, so that we don't have to
    //call AVIStreamLength for every new frame
    countFrames++;
}    

现在,我们能够用图像填充一个空流。但是,我们如何才能向现有流添加帧呢?嗯,首先,我们必须用第三个构造函数打开该流。

重新压缩视频流

public VideoStream(int aviFile, IntPtr aviStream){

    this.aviFile = aviFile;
    this.aviStream = aviStream;

    //read the stream's format
    Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER();
    int size = Marshal.SizeOf(bih);
    Avi.AVIStreamReadFormat(aviStream, 0, ref bih, ref size);
    Avi.AVISTREAMINFO streamInfo = GetStreamInfo(aviStream);

    //store the important format values
    this.frameRate = streamInfo.dwRate / streamInfo.dwScale;
    this.width = (int)streamInfo.rcFrame.right;
    this.height = (int)streamInfo.rcFrame.bottom;
    this.frameSize = bih.biSizeImage;
    this.countBitsPerPixel = bih.biBitCount;

    //get the count of frames that are already there
    int firstFrame = Avi.AVIStreamStart(aviStream.ToInt32());
    countFrames = 
       firstFrame + Avi.AVIStreamLength(aviStream.ToInt32());
}

如果您确定视频流未被压缩,则可以立即调用 AddFrame。否则,您必须解压缩现有帧,然后将它们重新压缩到新流中。

public AviManager DecompressToNewFile(String fileName, 
                                        bool recompress){
    //create a new AVI file
    AviManager newFile = new AviManager(fileName, false);

    //create a video stream in the new file
    this.GetFrameOpen();
    Bitmap frame = GetBitmap(0);
    VideoStream newStream = 
        newFile.AddVideoStream(recompress, frameRate, frame);

    //decompress each frame and add it to the new stream
    for(int n=1; n<countFrames; n++){
        frame = GetBitmap(n);
        newStream.AddFrame(frame);
    }

    this.GetFrameClose();

    return newFile;
}

DecompressToNewFile 会在新的文件中创建一个可写流的副本。您可以向此新流添加帧,关闭新的 AviManager 来保存它,然后将旧文件中的音频流添加到其中以完成复制。向视频添加帧并不容易,但这种方式可行。

分离流

有时,您可能有一个带有音频的视频文件,但您只需要无声视频或只需要音频。不必逐帧复制,您可以像平常一样打开流并使用 AVISaveV 导出它。这适用于所有类型的流,只有压缩选项不同。

public override void ExportStream(String fileName){
    Avi.AVICOMPRESSOPTIONS_CLASS opts = 
                new Avi.AVICOMPRESSOPTIONS_CLASS();

    //for video streams
    opts.fccType = (UInt32)Avi.mmioStringToFOURCC("vids", 0);
    opts.fccHandler = (UInt32)Avi.mmioStringToFOURCC("CVID", 0);

    //for audio streams
    //opts.fccType = (UInt32)Avi.mmioStringToFOURCC("auds", 0);
    //opts.fccHandler = (UInt32)Avi.mmioStringToFOURCC("CAUD", 0);

    //export the stream
    Avi.AVISaveV(fileName, 0, 0, 1, ref aviStream, ref opts);
}

从 Wave 文件导入音频

现在,我们能够从位图构建视频,并从中提取音频。音频是如何进入文件的?我们可以再次使用 AVISaveV,将视频和音频流合并到一个新文件中 - 但我们不必这样做。添加新音频流的最简单方法是将 Wave 文件作为一个只有一个流的 AVI 文件打开,然后复制该流。

public void AddAudioStream(String waveFileName){
    //open the wave file
    AviManager audioManager = 
        new AviManager(waveFileName, true);
    //get the wave sound as an audio stream...
    AudioStream newStream = audioManager.GetWaveStream();
    //...and add it to the file
    AddAudioStream(newStream);
    audioManager.Close();
}

public void AddAudioStream(AudioStream newStream){
    Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
    Avi.PCMWAVEFORMAT streamFormat = new Avi.PCMWAVEFORMAT();
    int streamLength = 0;

    //read header info, format and length, 
    //and get a pointer to the wave data
    IntPtr waveData = newStream.GetStreamData(
        ref streamInfo,
        ref streamFormat,
        ref streamLength);

    //create new stream
    IntPtr aviStream;
    Avi.AVIFileCreateStream(aviFile, out aviStream, 
                                      ref streamInfo);

    //add format new stream
    Avi.AVIStreamSetFormat(
        aviStream, 0,
        ref streamFormat,
        Marshal.SizeOf(streamFormat));

    //copy the raw wave data into the new stream
    Avi.AVIStreamWrite(
        aviStream, 0,
        streamLength,
        waveData,
        streamLength,
        Avi.AVIIF_KEYFRAME, 0, 0);

    Avi.AVIStreamRelease(aviStream);
}

从视频和音频中复制片段

我添加了此方法,因为许多人问我如何做到这一点。要从 X 秒复制到 Y 秒的视频流的一部分,必须根据帧率和秒数来计算第一个和最后一个帧的索引。对于 Wave 流,我们必须根据每秒采样数、每样本位数和请求的秒数来计算字节偏移量。其余的只是复制和粘贴

public AviManager CopyTo(String newFileName,
    int startAtSecond, int stopAtSecond) {
    AviManager newFile = new AviManager(newFileName, false);

    try {
        //copy video stream

        VideoStream videoStream = GetVideoStream();

        int startFrameIndex = 
             videoStream.FrameRate * startAtSecond;
        int stopFrameIndex = 
             videoStream.FrameRate * stopAtSecond;

        videoStream.GetFrameOpen();
        Bitmap bmp = videoStream.GetBitmap(startFrameIndex);

        VideoStream newStream = newFile.AddVideoStream(
             false,
             videoStream.FrameRate,
             bmp);

        for (int n = startFrameIndex + 1; 
                     n <= stopFrameIndex; n++) {
            bmp = videoStream.GetBitmap(n);
            newStream.AddFrame(bmp);
        }
        videoStream.GetFrameClose();

        //copy audio stream

        AudioStream waveStream = GetWaveStream();

        Avi.AVISTREAMINFO streamInfo = 
                         new Avi.AVISTREAMINFO();
        Avi.PCMWAVEFORMAT streamFormat = 
                         new Avi.PCMWAVEFORMAT();
        int streamLength = 0;
        IntPtr ptrRawData = waveStream.GetStreamData(
            ref streamInfo,
            ref streamFormat,
            ref streamLength);

        int startByteIndex = waveStream.CountSamplesPerSecond
               * startAtSecond
               * waveStream.CountBitsPerSample / 8;

        int stopByteIndex = waveStream.CountSamplesPerSecond
               * stopAtSecond
               * waveStream.CountBitsPerSample / 8;

        ptrRawData = 
          new IntPtr(ptrRawData.ToInt32() + startByteIndex);

        byte[] rawData = 
          new byte[stopByteIndex - startByteIndex];
        Marshal.Copy(ptrRawData, rawData, 0, rawData.Length);

        streamInfo.dwLength = rawData.Length;
        streamInfo.dwStart = 0;

        IntPtr unmanagedRawData = 
              Marshal.AllocHGlobal(rawData.Length);
        Marshal.Copy(rawData, 0, unmanagedRawData, 
                                     rawData.Length);

        newFile.AddAudioStream(unmanagedRawData,
              streamInfo,
              streamFormat,
              rawData.Length);

    } catch (Exception ex) {
        newFile.Close();
        throw ex;
    }

    return newFile;
}

如果您仍然对 AVI 视频感兴趣,请下载包装器库和演示应用程序。最后,我敢说:祝您在使用 AVIFile 时玩得开心!

已知问题

向现有流添加帧并不适用于所有视频编解码器和/或位图。您可能会遇到 StackOverflowException 或损坏的帧。如果您找出原因,请告诉我。

历史

  • 2004 年 7 月 9 日 - 更新了下载。
  • 2004 年 10 月 3 日 - 新方法 AviManager.CopyTo
  • 2004 年 12 月 18 日 - 新类 EditableVideoStreamAviPlayer,并修复了一些内存泄漏。
  • 2005 年 11 月 26 日 - 移除了 VideoStream.GetBitmap 中的性能优化。
  • 2006 年 1 月 1 日 - 修正了 VideoStream.GetFrameOpen 中无效的颜色深度和隔行扫描视频问题,新属性 VideoStream.FirstFrame。非常感谢 Michael Covington!
© . All rights reserved.