I'm trying to write a Delphi 6 app that takes video and audio and writes it to an AVI using the VFW services. I managed to get the video stream working properly. It plays fine in VLC or Windows Media Player. But when I then add an audio stream and try to play the output AVI the VLC player complains about the AVI file being broken. It will play the file and the video looks fine, but there is no audio. I tried adding the audio stream before writing the video stream and then after it. Neither worked.
Also, if I don't add an audio stream I can right-click the output AVI file in Windows Explorer and in the summary tab I can see the proper information for the video data in the stream. After adding the stream right-clicking instead shows a status message saying the AVI properties could not be read.
UPDATE: I am doing something really basically wrong. It turns out, something I am doing is blowing the top level header of the file. If I don't create the audio stream the file is fine and I see the usual header information at the very start of the file. The instant I make the call to create the audio stream, the second stream I create, the header at the very start of the output AVI file is completely blank (zeroes). Even if I don't make a call to AVIStreamSetFormat() or write any data (and even if I do), just making that second AVICreateStream() call wipes the very start of the file, when examining the file with a hex editor. What could I be doing that is causing this amount if "damage" to the output file.
My question are:
1) What am I doing wrong? 2) Do you know of a good sample that shows how to create an AVI with audio and video interleaved, especially one that shows how to write a compressed audio stream too?
Here is the code I am using to write the audio stream, after I have completely written out the video stream. The unit level variable FAvi has the TWaveFormatEx field (wfx) filled in already. I examined the contents and the fields are all set to valid values of 8000 Khz sample rate, 1 channel, 16 bits per channel, and format tag of 1 (WAVE_FORMAT_PCM). The block-align, data rate, etc. are all properly filled in too:
function TAviWriterWithCompression.addAudioFrame(dat: Pointer; numbytes: Cardinal): HRESULT;
var
bRetErr: boolean;
numsamps: LongInt;
ahdr: TAVISTREAMINFO;
hr: HRESULT;
lSampWritten, lBytesWritten: LONG;
begin
Result := S_OK;
bRetErr := true;
if Assigned(FAvi_) then
begin
if Assigned(dat) and (numbytes <> 0) then
begin
if not FAvi_.iserr then
begin
if FAvi_.wfx.nChannels <> 0 then
bRetErr := false
else
Result := LongInt(AVIERR_BADFORMAT);
end
else
Result := LongInt(AVIERR_ERROR);
end
else
Result := LongInt(AVIERR_BADPARAM);
end
else
Result := LongInt(AVIERR_BADHANDLE);
if bRetErr then
// =========================== EXIT POINT ==============
exit;
if FAvi_.wfx.wBitsPerSample <= 0 then
begin
Result := LongInt(AVIERR_BADFORMAT);
// =========================== EXIT POINT ==============
exit;
end; // if FAvi_.wfx.wBitsPerSample <= 0 then
numsamps := Trunc((numbytes * 8) / FAvi_.wfx.wBitsPerSample);
if ((numsamps * FAvi_.wfx.wBitsPerSample / 8) <> numbytes) then
begin
Result := LongInt(AVIERR_BADPARAM);
// =========================== EXIT POINT ==============
exit;
end; // if ((numsamps * FAvi_.wfx.wBitsPerSample/8) <> numbytes) then
if not Assigned(FAvi_.theAs) then
begin
ZeroMemory(@ahdr, sizeof(ahdr));
ahdr.fccType := streamtypeAUDIO;
ahdr.dwScale := FAvi_.wfx.nBlockAlign;
ahdr.dwRate := FAvi_.wfx.nSamplesPerSec*FAvi_.wfx.nBlockAlign;
ahdr.dwSampleSize := FAvi_.wfx.nBlockAlign;
ahdr.dwQuality := DWORD(-1);
hr := AVIFileCreateStream(FAvi_.pfile, FAvi_.theAs, ahdr);
if hr <> AVIERR_OK then
begin
Result := hr;
// Set the error flag.
FAvi_.iserr := true;
// =========================== EXIT POINT ==============
exit;
end; // if hr <> AVIERR_OK then
hr := AVIStreamSetFormat(FAvi_.theAs, 0, @FAvi_.wfx, sizeof(FAvi_.wfx));
if hr <> AVIERR_OK then
begin
Result := hr;
// Set the error flag.
FAvi_.iserr := true;
// =========================== EXIT POINT ==============
exit;
end; // if hr <> AVIERR_OK then
end; // if not Assigned(FAvi_.theAs) then
// now we can write the data
hr := AVIStreamWrite(FAvi_.theAs, FAvi_.nsamp, numsamps, dat, numbytes, AVIIF_KEYFRAME, @lSampWritten, @lBytesWritten);
if hr <> AVIERR_OK then
begin
Result := hr;
// Set the error flag in our utility object.
FAvi_.iserr := true;
// =========================== EXIT POINT ==============
exit;
end; // if hr <> AVIERR_OK then
Inc(FAvi_.nsamp, numsamps);
// Set the flag that tells it is no longer a virgin file and that
// attempting to set the compression is not allowed.
FIsVirginFile := false;
end;
Here is the code that cleans up both the audio and video stream when finishing up:
destructor TAviWriterWithCompression.Destroy;
begin
// Code goes here.
if Assigned(FAvi_) then
begin
// Release the streams.
if Assigned(FAvi_.theAs) then
begin
AVIStreamRelease(FAvi_.theAs);
FAvi_.theAs := nil;
end;
if Assigned(FAvi_.thePsCompressed) then
begin
AVIStreamRelease(FAvi_.thePsCompressed);
// FAvi_.thePsCompressed := nil;
end;
if Assigned(FAvi_.thePs) then
begin
AVIStreamRelease(FAvi_.thePs);
// FAvi_.thePs := nil;
end;
if Assigned(FAvi_.pfile) then
begin
AVIFileRelease(FAvi_.pfile);
// FAvi_.pfile := nil;
end;
AVIFileExit();
// FreeAndNil(FAvi_);
end; // if Assigned(FAvi_) then
inherited Destroy;
end;
Notify: @RemyLebau
VirtualDub
should be able to show you (not only) how to handle the audio streams and setup all the structures properly (see AudioSource.cpp, InputFileMP3.cpp and InputFileWAV.cpp). I've been satisfied with it few years ago when I was using it. – TLama