Delphi FFMPEG CUDA hardware accelerated video decoding generating shadows - delphi

I'm trying to use cuda hardware accelerated video decoding, loading from a .mp4 file, according to the code bellow, but getting the image with shadows
Specification:
FFMPEG 4.3
Delphi 10.3
NVIDIA GeForce RTX 3050 Laptop GPU 4Gb DDR6
Intel Core i5 11400H 16 Gb RAM
unit UVideoDecoding;
interface
uses
Vcl.Graphics, Winapi.Windows,
System.SysUtils, System.Classes,
libavcodec, libavdevice,
libavfilter, libswresample, libavformat, libavutil,
libswscale;
type
TDecoder = class
protected
fFormatCtx: PAVFormatContext;
fCodec: PAVCodec;
fCodecParams, fLocalCodecParams: PAVCodecParameters;
fCodecCtx: libavcodec.PAVCodecContext;
fPacket: PAVPacket;
fFrameIn, fFrameOut: PAVFrame;
fScaler: PSwsContext;
fVideoStreamIndex: Integer;
fHWPixelFormat: AVPixelFormat;
fOutputWidth: Integer;
fOutputHeight: Integer;
function DecodeVideoPacket: Integer;
public
function Load(pFileName: string): Integer;
end;
implementation
function TDecoder.Load(pFileName: string): Integer;
var
vCount: Integer;
vLocalCodec: PAVCodec;
vFileName: AnsiString;
vType: AVHWDeviceType;
vDecPackResponse: Integer;
begin
vType := av_hwdevice_find_type_by_name('cuda');
{vType = AV_HWDEVICE_TYPE_CUDA}
fFormatCtx := avformat_alloc_context;
vFileName := AnsiString(pFileName);
avformat_open_input(fFormatCtx, PAnsiChar(vFileName), nil, nil);
avformat_find_stream_info(fFormatCtx, nil);
for vCount := 0 to fFormatCtx.nb_streams - 1 do
begin
fLocalCodecParams := fFormatCtx.streams[vCount].codecpar;
case fLocalCodecParams.codec_id of
AVCodecID.AV_CODEC_ID_H264: vLocalCodec := avcodec_find_decoder_by_name('h264_cuvid');
AVCodecID.AV_CODEC_ID_HEVC: vLocalCodec := avcodec_find_decoder_by_name('hevc_cuvid');
AVCodecID.AV_CODEC_ID_MJPEG: vLocalCodec := avcodec_find_decoder_by_name('mjpeg_cuvid');
AVCodecID.AV_CODEC_ID_MPEG4: vLocalCodec := avcodec_find_decoder_by_name('mpeg4_cuvid');
else
vLocalCodec := avcodec_find_decoder(fLocalCodecParams.codec_id);
end;
if fLocalCodecParams.codec_type = AVMEDIA_TYPE_VIDEO then
begin
fVideoStreamIndex := vCount;
fCodec := vLocalCodec;
fCodecParams := fLocalCodecParams;
end;
end;
{fCodec.name = h264_cuvid}
fCodecCtx := avcodec_alloc_context3(fCodec);
var vHWDeviceCtx: PAVBufferRef;
try
if av_hwdevice_ctx_create(vHWDeviceCtx, vType, nil, nil, 0) >= 0 then
fCodecCtx.hw_device_ctx := av_buffer_ref(vHWDeviceCtx);
finally
av_buffer_unref(vHWDeviceCtx);
end;
avcodec_open2(fCodecCtx, fCodec, nil);
{
fCodecCtx.codec_id = AV_CODEC_ID_H264
fCodecCtx.pix_fmt = AV_PIX_FMT_MMAL
}
fFrameIn := av_frame_alloc;
fFrameOut := av_frame_alloc;
fFrameOut.format := Integer(AV_PIX_FMT_BGRA);
fFrameOut.width := 640;
fFrameOut.height := 480;
//On getting sws_context I've tried srcFormat = AV_PIX_FMT_MMAL but the result was nil
fScaler := sws_getContext(fCodecCtx.Width, fCodecCtx.Height, AV_PIX_FMT_NV12{fCodecCtx.pix_fmt},
fOutputWidth, fOutputHeight, AV_PIX_FMT_BGRA, SWS_BILINEAR, nil, nil, nil);
fPacket := av_packet_alloc;
while (av_read_frame(fFormatCtx, fPacket) >= 0) do
begin
if fPacket.stream_index = fVideoStreamIndex then
begin
vDecPackResponse := DecodeVideoPacket;
av_packet_unref(fPacket);
if vDecPackResponse < 0 then
Break;
end
end;
Exit(0);
end;
function TDecoder.DecodeVideoPacket: Integer;
var
vResponse: Integer;
vBmp: Vcl.Graphics.TBitmap;
vScaleResult, vSize: Integer;
vBuffer: PByte;
vSWFrame, vTMPFrame: pAVFrame;
begin
Result := 0;
vResponse := avcodec_send_packet(fCodecCtx, fPacket);
if vResponse < 0 then
Exit(vResponse);
while (vResponse >= 0) do
begin
vResponse := avcodec_receive_frame(fCodecCtx, fFrameIn);
if (vResponse = AVERROR_EAGAIN) or (vResponse = AVERROR_EOF) then
Break
else if vResponse < 0 then
Exit(vResponse);
if vResponse >= 0 then
begin
vSWFrame := av_frame_alloc;
if av_hwframe_transfer_data(vSWFrame, fFrameIn, 0) >= 0 then
vTMPFrame := vSWFrame
else
vTMPFrame := fFrameIn;
vSize := av_image_get_buffer_size(AVPixelFormat(vTMPFrame.format), vTMPFrame.width, vTMPFrame.height, 1);
vBuffer := av_malloc(vSize);
if Assigned(vBuffer) then
av_image_copy_to_buffer(vBuffer, vSize, #vTMPFrame.data, #vTMPFrame.linesize, AVPixelFormat(vTMPFrame.format), vTMPFrame.width, vTMPFrame.height, 1);
MoveMemory(vTMPFrame.data[0], vBuffer, vSize);
vTMPFrame.data[0] := vTMPFrame.data[0] + vTMPFrame.linesize[0] * (fCodecCtx.height - 1);
vTMPFrame.linesize[0] := vTMPFrame.linesize[0] * -1;
vTMPFrame.data[1] := vTMPFrame.data[1] + vTMPFrame.linesize[1] * (fCodecCtx.height div 2 - 1);
vTMPFrame.linesize[1] := vTMPFrame.linesize[1] * -1;
vTMPFrame.data[2] := vTMPFrame.data[2] + vTMPFrame.linesize[2] * (fCodecCtx.height div 2 - 1);
vTMPFrame.linesize[2] := vTMPFrame.linesize[2] * -1;
vScaleResult := sws_scale(fScaler, #vTMPFrame.Data, #vTMPFrame.Linesize, 0,
fCodecCtx.Height, #fFrameOut.data, #fFrameOut.Linesize);
if vScaleResult <= 0 then
begin
Break;
end;
vBmp := Vcl.Graphics.TBitmap.Create;
try
vBmp.Height := fOutputHeight;
vBmp.Width := fOutputWidth;
vBmp.PixelFormat := TPixelFormat.pf32bit;
MoveMemory(PByte(vBmp.ScanLine[vBmp.Height -1]),
fFrameOut.data[0], fOutputHeight * fFrameOut.linesize[0]);
//Renders BMP on main thread
// if Assigned(fOnBitmap) then
// fOnBitmap(vBmp);
finally
av_frame_unref(vSWFrame);
av_frame_free(vTMPFrame);
av_freep(#vBuffer);
FreeAndNil(vBmp);
Result := 1;
end;
end;
end;
end;
end.
I've tried using different pixelformats but none of them properly decoded the video.

Related

Unable to properly turn a YUV420P image to RGBA

I've been trying to make a VideoPlayer following this tutorial http://dranger.com/ffmpeg in order to teach myself how to use libav but I've wanted to make it the using Delphi VCL and DirectSound instead of SDL.
Unfortunately I got stuck when trying to convert a YUV420P frame to a RGBA one, even though I can write the bytes on a TBitmap the color seems to be a bit off =>
I've tried using sws_setColorspaceDetails to fix it but without success.
It doesn't seem to matter which .mp4/.mkv I use, the color is always off.
If there are people in the picture their faces are blue for example.
I got the headers from this repo => https://github.com/Laex/Delphi-FFMPEG
I used that 5 second BigBuckBunny clip that people use on the libav "Hello World"
The binaries I get from https://github.com/BtBn/FFmpeg-Builds/releases?page=4
Here's the code
unit uVideoPlayer;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
libavcodec, libavdevice, libavfilter,
libavformat, libavutil, libswscale;
type
TfrmMain = class(TForm)
btnPlay: TButton;
edtFile: TEdit;
mem: TMemo;
img: TImage;
procedure FormCreate(Sender: TObject);
procedure btnPlayClick(Sender: TObject);
end;
var
frmMain: TfrmMain;
implementation
uses
Uutils,
USmoothResize,
System.AnsiStrings;
{$R *.dfm}
function DecodePacket(pPacket: PAVPacket; pCodecCtx: PAVCodecContext;
pSwsCtx: PSWSContext; pFrame, pFrameRGB: PAVFrame): Integer;
var
vResponse: Integer;
vSrcBmp, vDstBmp: TBitmap;
begin
vResponse := avcodec_send_packet(pCodecCtx, pPacket);
if vResponse < 0 then
Exit(vResponse);
while (vResponse >= 0) do
begin
vResponse := avcodec_receive_frame(pCodecCtx, pFrame);
if (vResponse = AVERROR_EAGAIN) or (vResponse = AVERROR_EOF) then
Break
else if vResponse < 0 then
Exit(vResponse);
if vResponse >= 0 then
begin
frmMain.mem.Lines.Add(Format('Frame %d (type=%s, size=%d bytes, format=%d) pts %d key_frame %d [DTS %d]',
[
pCodecCtx.frame_number,
av_get_picture_type_char(pFrame.pict_type),
pFrame.pkt_size,
pFrame.format,
pFrame.pts,
pFrame.key_frame,
pFrame.coded_picture_number
]));
if (pFrame.format <> Integer(AV_PIX_FMT_YUV420P)) then
frmMain.mem.Lines.Add('Not a grayscale image!');
pFrame.data[0] := pFrame.data[0] + pFrame.linesize[0] * (pCodecCtx.height - 1);
pFrame.linesize[0] := pFrame.linesize[0] * -1;
pFrame.data[1] := pFrame.data[1] + pFrame.linesize[1] * (pCodecCtx.height div 2 - 1);
pFrame.linesize[1] := pFrame.linesize[1] * -1;
pFrame.data[2] := pFrame.data[2] + pFrame.linesize[2] * (pCodecCtx.height div 2 - 1);
pFrame.linesize[2] := pFrame.linesize[2] * -1;
sws_scale(pSwsCtx, #pFrame^.Data, #pFrame^.Linesize, 0,
pCodecCtx.height, #pFrameRGB.data[0], #pFrameRGB.Linesize[0]);
vSrcBmp := TBitmap.Create;
vDstBmp := nil;
try
vSrcBmp.Height := pCodecCtx.height;
vSrcBmp.Width := pCodecCtx.width;
vSrcBmp.PixelFormat := TPixelFormat.pf32bit;
MoveMemory(vSrcBmp.ScanLine[vSrcBmp.Height -1], pFrameRGB.data[0],
pFrameRGB.linesize[0] * pCodecCtx.Height);
vDstBmp := TBitmap.Create;
vDstBmp.Width := frmMain.img.Width;
vDstBmp.Height := frmMain.img.Height;
vDstBmp.PixelFormat := TPixelFormat.pf32bit;
SmoothResize(vSrcBmp, vDstBmp);
frmMain.img.Picture.Assign(vDstBmp);
Application.ProcessMessages;
finally
FreeAndNil(vSrcBmp);
FreeAndNil(vDstBmp);
end;
end;
end;
Exit(0);
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
libavformat.av_register_all;
end;
procedure TfrmMain.btnPlayClick(Sender: TObject);
type
TQuadInt = array [0..3] of Integer;
PQuadInt = ^TQuadInt;
var
vCount, vVideoStreamIndex, vDecPackResponse: Integer;
vFileName: AnsiString;
// vI1, vI2: PQuadInt;
vFormatCtx: PAVFormatContext;
vCodec, vLocalCodec: PAVCodec;
vCodecParams, vLocalCodecParams: PAVCodecParameters;
vCodecCtx: PAVCodecContext;
vFrame, vFrameRGB: PAVFrame;
vPacket: PAVPacket;
vImgConvertCtx: PSWSContext;
begin
vFormatCtx := avformat_alloc_context;
if vFormatCtx <> nil then
mem.Lines.Add('Alloc format context succesful!')
else
Exit;
vFileName := AnsiString(edtFile.Text);
if avformat_open_input(vFormatCtx, PAnsiChar(vFileName), nil, nil) = 0 then
mem.Lines.Add('Open input success!')
else
Exit;
if avformat_find_stream_info(vFormatCtx, nil) = 0 then
mem.Lines.Add('Find stream info AVFormat success!')
else
Exit;
vCodec := nil;
vCodecParams := nil;
vVideoStreamIndex := -1;
for vCount := 0 to vFormatCtx.nb_streams - 1 do
begin
vLocalCodecParams := vFormatCtx.streams[vCount].codecpar;
mem.Lines.Add(Format('[%d] AVStream.time_base before open codec %d/%d',
[vCount, vFormatCtx.streams[vCount].time_base.num, vFormatCtx.streams[vCount].time_base.den]));
mem.Lines.Add(Format('[%d] AVStream.r_frame_rate before open codec %d/%d',
[vCount, vFormatCtx.streams[vCount].r_frame_rate.num, vFormatCtx.streams[vCount].r_frame_rate.den]));
mem.Lines.Add(Format('[%d] AVStream.start_time %" PRId64',
[vCount, vFormatCtx.streams[vCount].start_time]));
mem.Lines.Add(Format('[%d] AVStream.duration %" PRId64',
[vCount, vFormatCtx.streams[vCount].duration]));
vLocalCodec := avcodec_find_decoder(vLocalCodecParams.codec_id);
if vLocalCodec = nil then
Continue;
if vLocalCodecParams.codec_type = AVMEDIA_TYPE_VIDEO then
begin
vVideoStreamIndex := vCount;
vCodec := vLocalCodec;
vCodecParams := vLocalCodecParams;
mem.Lines.Add(Format('[%d] Video codec params %dx%d',
[vCount, vLocalCodecParams.width, vLocalCodecParams.height]));
end;
mem.Lines.Add(Format('[%d] CodecName: %s | CodecID: %d | BitRate: %d',
[vCount, vLocalCodec.Name, Integer(vLocalCodec.ID), vLocalCodecParams.bit_rate]));
end;
if vVideoStreamIndex = -1 then
Exit;
vCodecCtx := avcodec_alloc_context3(vCodec);
if vCodecCtx <> nil then
mem.Lines.Add('Codec context allocation succesful!')
else
Exit;
if avcodec_parameters_to_context(vCodecCtx, vCodecParams) = 0 then
mem.Lines.Add('Params to Ctx succesful!')
else
Exit;
if avcodec_open2(vCodecCtx, vCodec, nil) = 0 then
mem.Lines.Add('AvCodec open succesful!')
else
Exit;
vFrame := av_frame_alloc;
if (vFrame = nil) then
Exit;
vFrameRGB := av_frame_alloc;
if (vFrameRGB = nil) then
Exit;
vFrameRGB.format := Integer(AV_PIX_FMT_RGBA);
vFrameRGB.width := vCodecCtx.width;
vFrameRGB.height := vCodecCtx.height;
if (av_frame_get_buffer( vFrameRGB, 32 )) <> 0 then
Exit;
vImgConvertCtx := sws_alloc_context();
av_opt_set_int(vImgConvertCtx, 'sws_flags', SWS_POINT, 0);
av_opt_set_int(vImgConvertCtx, 'srcw', vCodecCtx.Width, 0);
av_opt_set_int(vImgConvertCtx, 'srch', vCodecCtx.Height, 0);
av_opt_set_int(vImgConvertCtx, 'src_format', Integer(vCodecCtx.pix_fmt), 0);
av_opt_set_int(vImgConvertCtx, 'dstw', vCodecCtx.Width, 0);
av_opt_set_int(vImgConvertCtx, 'dsth', vCodecCtx.Height, 0);
av_opt_set_int(vImgConvertCtx, 'dst_format', Integer(AV_PIX_FMT_RGBA), 0);
// vI1 := Pointer(sws_getCoefficients(Integer(0)));
// vI2 := Pointer(sws_getCoefficients(Integer(AVCOL_SPC_BT709)));
// sws_setColorspaceDetails(vImgConvertCtx, Pointer(#vI1), 1, Pointer(#vI2), 1,
// 0, 1 shl 16, 1 shl 16);
sws_init_context(vImgConvertCtx, nil, nil);
vPacket := av_packet_alloc;
if (vPacket = nil) then
Exit;
while (av_read_frame(vFormatCtx, vPacket) >= 0) do
begin
if vPacket.stream_index = vVideoStreamIndex then
begin
vDecPackResponse := DecodePacket(vPacket, vCodecCtx,
vImgConvertCtx, vFrame, vFrameRGB);
if vDecPackResponse < 0 then
Break;
end;
av_frame_unref(vFrame);
av_packet_unref(vPacket);
end;
av_frame_unref(vFrameRGB);
av_frame_free(vFrameRGB);
avformat_close_input(vFormatCtx);
av_packet_free(vPacket);
av_frame_free(vFrame);
avcodec_free_context(vCodecCtx);
sws_freeContext(vImgConvertCtx);
end;
end.
Just needed to change the target format from AV_PIX_FMT_RGBA to AV_PIX_FMT_BGRA.
Thanks #Andreas Rejbrand
Here's how the bunny looks now

MAPISendMail access violation

I have a problem with MapiSendMail function of MAPI32.dll. Everything seems fine, message is completed, then I send it by winapi function, and i get an Access violation error, it happend in MAPISendMail. Here's the fragment of the code:
MAPIModule := LoadLibrary(PWideChar(MAPIDLL));
if MAPIModule = 0 then
Result := -1
else
try
#SM := GetProcAddress(MAPIModule, 'MAPISendMail');
if #SM <> nil then
begin
Result := SM(0, application.Handle, Msg, MAPI_DIALOG {or MAPI_LOGON_UI}, 0);
end
else
Result := 1;
finally
end;
Also I was trying to change GetProcAddres to MAPISendMailW or MAPISendMailHelper, but then #SM was nil.
#Edit1
function TMail._SendMAPIEmail(const aTo, aAtts: array of AnsiString; const body, subject, SenderName, SenderEmail: string; ShowError: Boolean = true): Integer;
var
SM: TFNMapiSendMail;
Msg: MapiMessage;
lpSender: MapiRecipDesc;
Recips: array of MapiRecipDesc;
Att: array of MapiFileDesc;
TempAttNames: array of pAnsiChar;
TempAttNamesAnsi: array of AnsiString;
TempAttPaths: array of pAnsiChar;
TempRecip: array of pAnsiChar;
p1, LenTo, LenAtts: Integer;
MAPIModule: HModule;
sError: String;
i: integer;
begin
try
FillChar(Msg, SizeOf(Msg), 0);
{ get the length of all arrays passed to this function }
LenTo := length(aTo);
if Trim(aAtts[0]) <> '' then
LenAtts := length(aAtts)
else
LenAtts := 0;
{ ... }
SetLength(Recips, LenTo);
SetLength(TempRecip, LenTo);
Setlength(Att, LenAtts);
SetLength(TempAttNames, LenAtts);
SetLength(TempAttPaths, LenAtts);
SetLength(TempAttNamesAnsi, LenAtts);
{ to }
for p1 := 0 to LenTo - 1 do
begin
FillChar(Recips[p1], SizeOf(Recips[p1]), 0);
Recips[p1].ulReserved := 0;
Recips[p1].ulRecipClass := MAPI_TO;
{ Upgrade }
Recips[p1].lpszName := '';
TempRecip[p1] := pAnsichar(aTo[p1]);
Recips[p1].lpszAddress := TempRecip[p1];
end;
{ atts }
for p1 := 0 to LenAtts - 1 do
begin
FillChar(Att[p1], SizeOf(Att[p1]), 0);
FillChar(TempAttPaths[p1], SizeOf(pAnsiChar), 0);
FillChar(TempAttNames[p1], SizeOf(pAnsiChar), 0);
FillChar(TempAttNamesAnsi[01], SizeOf(AnsiChar), 0);
Att[p1].ulReserved := 0;
Att[p1].flFlags := 0;
Att[p1].nPosition := Cardinal($FFFFFFFF);
{ Upgrade }
TempAttPaths[p1] := pAnsichar(aAtts[p1]);
Att[p1].lpszPathName := TempAttPaths[p1];
TempAttNamesAnsi[p1] := AnsiString((ExtractFileName(string(aAtts[p1]))));
TempAttNames[p1] := pAnsiChar(TempAttNamesAnsi[p1]);
Att[p1].lpszFileName := TempAttNames[p1];
end;
{ fill the message }
with Msg do
begin
ulReserved := 0;
if subject <> '' then
{ Upgrade }
lpszSubject := pAnsichar(AnsiString(subject));
if body <> '' then
{ Upgrade }
lpszNoteText := pAnsichar(AnsiString(body));
if SenderEmail <> '' then
begin
lpSender.ulRecipClass := MAPI_ORIG;
if SenderName = '' then
lpSender.lpszName := pAnsichar(AnsiString(SenderEmail))
else
lpSender.lpszName := pAnsichar(AnsiString(SenderName));
lpSender.lpszAddress := pAnsichar(AnsiString(SenderEmail));
lpSender.ulEIDSize := 0;
lpSender.lpEntryID := nil;
lpOriginator := #lpSender;
end
else
Msg.lpOriginator := nil;
Msg.lpszMessageType := nil;
Msg.lpszDateReceived := nil;
Msg.lpszConversationID := nil;
Msg.flFlags := 0;
Msg.nRecipCount := LenTo;
Msg.lpRecips := #Recips[0];
Msg.nFileCount := LenAtts;
Msg.lpFiles := #Att[0];
end;
MAPIModule := LoadLibrary(PWideChar(MAPIDLL));
if MAPIModule = 0 then
Result := -1
else
try
#SM := GetProcAddress(MAPIModule, 'MAPISendMail');
if #SM <> nil then
begin
//Result := MapiSendMail(0, application.Handle, Msg, MAPI_DIALOG, 0);
Result := SM(0, 0, Msg, MAPI_DIALOG {or MAPI_LOGON_UI}, 0);
end
else
Result := 1;
finally
if Assigned(Att) and (Msg.nFileCount > 0) then
begin
for i := 0 to Msg.nFileCount - 1 do
begin
if Assigned(Att[i].lpszPathName) then
Att[i].lpszPathName := nil;
if Assigned(Att[i].lpszFileName) then
Att[i].lpszFileName := nil;
//FreeMem(Att[i].lpszPathName);
//Dispose(Att[i].lpszPathname);
//StrDispose(Att[i].lpszPathName);
//Dispose(Att[i].lpszFileName);
//StrDispose(Att[i].lpszFileName);
end;
Att := nil;
end;
if Assigned(Recips) and (Msg.nRecipCount > 0) then
begin
for i := 0 to Msg.nRecipCount - 1 do
begin
if Assigned(Recips[i].lpszName) then
Recips[i].lpszName := nil;
if Assigned(Recips[i].lpszAddress) then
Recips[i].lpszAddress := nil;
//if Assigned(Recips[i].lpszName) then
//Dispose(Recips[i].lpszName);
//if Assigned(Recips[i].lpszAddress) then
//Dispose(Recips[i].lpszAddress);
end;
Recips := nil;
end;
end;
Under Win32
Under Win32 it should not be a problem. Just first try calling MapiSendMail with very simple MapiMessage and if it will work, add complexity little by little. Your code is just too complex to debug it visually. Did you call MapiSendMail with very simple MapiMessage, just for testing? Please try the following code, it works for sure:
procedure TestSendExA(const APath1, ACaption1, APath2, ACaption2: AnsiString);
var
R: Integer;
MSG: TMapiMessage;
F: Array [0..1] of TMapiFileDesc;
Recipients: array[0..1] of TMapiRecipDesc;
Originator : array[0..0] of TMapiRecipDesc;
begin
if not FileExists(APath1) or not FileExists(APath2) then raise Exception.Create('File not found');
FillChar(Msg, SizeOf(Msg), 0);
Msg.lpszSubject := 'testo';
Msg.lpszNoteText := 'Hi there!';
Msg.lpszDateReceived := '2015/01/25 12:34';
Msg.lpszConversationId := '1234.test#ritlabs.com';
Msg.flFlags := MAPI_RECEIPT_REQUESTED;
FillChar(Recipients, SizeOf(Recipients), 0);
with Recipients[0] do
begin
ulRecipClass := MAPI_TO;
lpszName := 'Maxim Masiutin';
lpszAddress := 'maxim.test#ritlabs.com';
end;
with Recipients[1] do
begin
ulRecipClass := MAPI_CC;
lpszName := 'Vasilii Pupkin';
lpszAddress := 'pupkin.test#ritlabs.com';
end;
FillChar(Originator, SizeOf(Originator), 0);
with Originator[0] do
begin
ulRecipClass := MAPI_TO;
lpszName := 'Maxim Masiutin';
lpszAddress := 'max#ritlabs.com';
end;
Msg.lpOriginator := #Originator;
Msg.nRecipCount := 2;
Msg.lpRecips := #Recipients;
Msg.nFileCount := 2;
Msg.lpFiles := #F;
FillChar(F, SizeOf(F), 0);
F[0].lpszPathName := PAnsiChar(APath1);
F[0].lpszFileName := PAnsiChar(ACaption1);
F[1].lpszPathName := PAnsiChar(APath2);
F[1].lpszFileName := PAnsiChar(ACaption2);
R := MAPISendMail(MapiSession, 0, Msg, 0, 0);
end;
The MapiSession in the above example is a handle to the session returned by MapiLogon.
This sample code requires that you pass two valid file paths to valid files in APath1 and APath2.
Under Win64
It is the record alignment of MapiMessage and other records that it is important when you work with Simple MAPI from Delphi: (1) make sure the records don't have "packed" prefix; and (2) make sure you have {$A8} compiler directive is explicitly specified before first record definition. This will work fine under both Win32 and Win64.

Search for an array of bytes in another process

I wish to search for an array of bytes inside another process. I am using VirtualQueryEx and ReadProcessMemory but i am unsure of the correct what to do this.
Here is how my code looks so far:
procedure TForm1.Button2Click(Sender: TObject);
const
Target: array[0..7] of byte = ($A0, $19, $40, $2B, $F6, $7F, $00, $00);
var
Mbi: TMemoryBasicInformation;
Handle: THandle;
buff: array of byte;
hWin, ProcID, BuffSize: Cardinal;
Addr: DWORD_PTR;
BytesRead: NativeUInt;
i: integer;
begin
hWin := FindWindow(nil, 'Minesweeper');
if hWin > 0 then
GetWindowThreadProcessID(hWin, #ProcId);
if ProcId > 0 then
begin
Handle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, ProcId);
if Handle <> 0 then
begin
while (VirtualQueryEx(Handle, Ptr(Addr), Mbi, SizeOf(Mbi)) <> 0) do
begin
SetLength(buff, BuffSize);
if ReadProcessMemory(Handle, Mbi.BaseAddress, Buff, Mbi.RegionSize, BytesRead) then
begin
for i := 0 to Length(Buff) do
if CompareMem(#Buff[i], #Target[1], Length(Target)) then
begin
ShowMessage('Found');
end;
end;
if Addr + BuffSize < Addr then
break;
Addr := Addr + BuffSize;
end;
SetLength(buff, 0);
CloseHandle(Handle);
end;
end;
end;
Program freezes.
There are a number of issues with your code.
Rather than spoonfeed the solution I'm going to explain where to get the info.
Every local variable that you use, must be initialized beforehand.
Initialize local variables
The following vars are not initialized:
{mbi - Set to zero using} FillChar(mbi, SizeOf(Mbi), #0);
BuffSize:= 1024*4 + SizeOf(Target);
//Also add a
const
DefaultBufSize = 1024*4;
Do not start reading from 0
Instead initialize Addr to the starting address of the process using GetModuleInfo
Don't forget to initialize the mi: TModuleInfo structure using:
FillChar(mi, SizeOf(mi), #0);
Read a 4k+ buffer
Now keep loading buffers of BuffSize, but only increase Addr with DefaultBuffSize.
Check how many bytes are actually read
Make sure to check the BytesRead parameter and reduce the BuffSize if fewer bytes are read (or you'll get access violations).
Utilize a smart search algorithm
Use the following routine to find the string:
Boyer-Moore-Horspool string search algorithm - Wiki
(Thanks to: Dorin Duminica)
{$PointerMath on}
function FindMem(P1: pointer; Size1: Cardinal; P2: Pointer; Size2: Cardinal): Integer;
var
i,j,k: Integer;
LenPattern: Integer;
LenValue: Integer;
SkipTable: array[byte] of Integer;
Found: Boolean;
B: Byte;
function __SameByte: Boolean;
begin
Result := (PByte(P1)[i] = PByte(P2)[j])
end; // function __SameChar: Boolean;
begin
Found := False;
Result := -1;
LenPattern := size2;
if LenPattern = 0 then begin
Result := 0;
Found := True;
end; // if LenPattern = 0
for B:= low(byte) to high(byte) do SkipTable[B]:= LenPattern;
for k:= 1 to LenPattern - 1 do SkipTable[PByte(P2)[k]]:= LenPattern - k;
k:= LenPattern + 0;
LenValue := size1;
while (not Found) and (k <= LenValue) do begin
i := k;
j := LenPattern;
while (j >= 1) do begin
if __SameByte then begin
j := j -1;
i := i -1;
end else
j := -1;
if j = 0 then begin
Result := i;
Found := True;
end; // if j = 0
k := k + SkipTable[PByte(P1)[k]];
end; // while (j >= 1)
end; // while (not Found) and (k <= Size1)
end;
See here for info on GetModuleInfo
Good luck

TTreeView custom draw item width

I use the OnCustomDrawItem event to draw a TTreeView like this :
Here is my code :
procedure Tform1.trvArbreCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var
vRect : TRect;
vBmp : TBitmap;
vBmpRect : TRect;
vTreeView : TTreeView;
vBarreInfo : TScrollInfo;
vDeltaX : Integer;
begin
DefaultDraw := False;
vTreeView := TTreeView(Sender);
vRect := Node.DisplayRect(False);
vBmp := TBitmap.Create();
FillChar(vBarreInfo, SizeOF(vBarreInfo), 0);
vBarreInfo.cbSize := SizeOf(vBarreInfo);
vBarreInfo.fMask := SIF_RANGE or SIF_POS;
if GetScrollInfo(trvArbre.Handle, SB_HORZ, vBarreInfo) then
begin
if vBarreInfo.nMax > vRect.Right - vRect.Left then
begin
vBmp.Width := vBarreInfo.nMax + 1;
vBmp.Height := vRect.Bottom - vRect.Top;
vDeltaX := vBarreInfo.nPos;
end
else
begin
vBmp.Width := vRect.Right - vRect.Left;
vBmp.Height := vRect.Bottom - vRect.Top;
vDeltaX := 0;
end;
end
else
begin
vBmp.Width := vRect.Right - vRect.Left;
vBmp.Height := vRect.Bottom - vRect.Top;
vDeltaX := 0;
end;
vBmpRect := Rect(0, 0, vBmp.Width, vBmp.Height);
if cdsSelected in State then
begin
vBmp.Canvas.Brush.Color := cMenuDownFond;
vBmp.Canvas.Pen .Color := cMenuDownBordure;
end
else if cdsHot in State then
begin
vBmp.Canvas.Brush.Color := cMenuSurvolFond;
vBmp.Canvas.Pen .Color := cMenuSurvolBordure;
end
else
begin
vBmp.Canvas.Brush.Color := clWhite;
vBmp.Canvas.Pen .Color := clwhite;
end;
vBmp.Canvas.Rectangle(vBmpRect);
vBmpRect.Left := vBmpRect.Left + 3;
vBmpRect.Left := vBmpRect.Left + (Node.Level * vTreeView.Indent);
if Node.StateIndex >= 0 then
begin
vTreeView.StateImages.Draw(vBmp.Canvas, vBmpRect.Left, vBmpRect.Top, Node.StateIndex);
end;
vBmpRect.Left := vBmpRect.Left + 18;
vTreeView.Images.Draw(vBmp.Canvas, vBmpRect.Left, vBmpRect.Top, Node.ImageIndex);
vBmpRect.Left := vBmpRect.Left + 18 + 3;
vBmp.Canvas.Font := vTreeView.Font;
DrawText
(
vBmp.Canvas.Handle,
PChar(Node.Text),
Length(Node.Text),
vBmpRect,
DT_SINGLELINE or DT_LEFT or DT_VCENTER or DT_NOPREFIX or DT_END_ELLIPSIS
);
BitBlt
(
Sender.Canvas.Handle,
vRect.Left,
vRect.Top,
vRect.Right - vRect.Left,
vRect.Bottom - vRect.Top,
vBmp.Canvas.Handle,
vDeltaX,
0,
SRCCOPY
);
FreeAndNil(vBmp);
end;
My problem is that the node "My last node wich is not too long" is not too long to justify the presence of the horizontal scrollbar.
When I set DefaultDraw to true I obtain :
It seems that the width of the node is computed with a font I don't use.
I tried to change the font of the canvas, to use Windows API, to use the OnAdvancedCustomDrawItem with no result.
Thanks.
I use Delphi 7. I copied ComCtrls.pas in the folder of my application. I changed procedure TCustomTreeView.CNNotify(var Message: TWMNotify);. Line 8979 from Result := Result or CDRF_SKIPDEFAULT to Result := Result or CDRF_SKIPDEFAULT; and I commented line 8980 else if FCanvasChanged then in order to simulate DefaultDraw=True and FCanvasChanged even if I set DefaultDraw to False in event et don't change font. After a lot of tests, I don't see any caveats.

what is the simpliest way to play sound from array data in delphi

Is there any simple function? I am searching something like that
Play(#data, 44000, 100 {time});
I have worked quite a lot with PCM audio manipulation. I always use this function when playing short sequences of custom waveform audio data:
var
PlaySoundStopper: PBoolean;
SoundPlayerActive: boolean = false;
procedure PlaySound(const Sound: TASSound);
var
hWave: HWAVEOUT;
hdr: TWaveHdr;
buf: PAnsiChar;
fmt: TWaveFormatEx;
i: Integer;
n: Integer;
begin
try
with fmt do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := length(Sound.Channels);
nSamplesPerSec := Sound.SampleRate;
wBitsPerSample := 32;
nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
nBlockAlign := nChannels * wBitsPerSample div 8;
cbSize := 0;
end;
GetMem(buf, fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample));
if length(Sound.Channels) = 1 then
CopyMemory(buf, #(Sound.Channels[0, 0]), length(Sound.Channels[0]) * sizeof(TASWaveformSample))
else
for i := 0 to high(Sound.Channels[0]) do
for n := 0 to high(Sound.Channels) do
CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), #(Sound.Channels[n, i]), sizeof(TASWaveformSample));
if waveOutOpen(#hWave, WAVE_MAPPER, #fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));
ZeroMemory(#hdr, sizeof(hdr));
with hdr do
begin
lpData := buf;
dwBufferLength := fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample);
dwFlags := 0;
end;
try
SoundPlayerActive := true;
waveOutPrepareHeader(hWave, #hdr, sizeof(hdr));
waveOutWrite(hWave, #hdr, sizeof(hdr));
sleep(500);
while waveOutUnprepareHeader(hWave, #hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
if PlaySoundStopper^ then
begin
waveOutPause(hWave);
waveOutUnprepareHeader(hWave, #hdr, sizeof(hdr));
break;
end
else
sleep(100);
finally
SoundPlayerActive := false;
waveOutClose(hWave);
FreeMem(buf);
end;
except
on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'Sound Playback Error', MB_ICONERROR);
end;
end;
where
type
TASWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
TASWaveformSamples = packed array of TASWaveformSample; // one channel
PASSound = ^TASSound;
TASSound = record
Channels: packed array of TASWaveformSamples;
SampleRate: cardinal;
end;
A perhaps better way, is to use a thread for the playing. Then I do
var
OwnerForm: HWND; // = 0;
SndSource: PASSound; // = nil;
ThreadPlaying: boolean; // = false;
type
TSoundPlayerThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implemented as
procedure TSoundPlayerThread.Execute;
var
hWave: HWAVEOUT;
hdr: TWaveHdr;
buf: PAnsiChar;
fmt: TWaveFormatEx;
i: Integer;
n: Integer;
begin
ThreadPlaying := true;
try
try
if not Assigned(SndSource) then
Exit;
with fmt do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := length(SndSource^.Channels);
nSamplesPerSec := SndSource^.SampleRate;
wBitsPerSample := 32;
nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
nBlockAlign := nChannels * wBitsPerSample div 8;
cbSize := 0;
end;
GetMem(buf, fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample));
if length(SndSource^.Channels) = 1 then
CopyMemory(buf, #(SndSource^.Channels[0, 0]), length(SndSource^.Channels[0]) * sizeof(TASWaveformSample))
else
for i := 0 to high(SndSource^.Channels[0]) do
for n := 0 to high(SndSource^.Channels) do
CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), #(SndSource^.Channels[n, i]), sizeof(TASWaveformSample));
if waveOutOpen(#hWave, WAVE_MAPPER, #fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));
ZeroMemory(#hdr, sizeof(hdr));
with hdr do
begin
lpData := buf;
dwBufferLength := fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample);
dwFlags := 0;
end;
waveOutPrepareHeader(hWave, #hdr, sizeof(hdr));
waveOutWrite(hWave, #hdr, sizeof(hdr));
sleep(500);
while waveOutUnprepareHeader(hWave, #hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
begin
sleep(100);
if Terminated then
waveOutReset(hWave);
end;
waveOutClose(hWave);
FreeMem(buf);
except
on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'TSoundPlayerThread', MB_ICONERROR);
end;
finally
ThreadPlaying := false;
end;
end;
Wave Audio Package has TLiveAudioPlayer component. It plays audio from buffer.
The Win32 API PlaySound function can play standard RIFF-encoded audio (such as WAV audio) from a memory block by using its SND_MEMORY flag. Alternatively, if the audio is in the app's resources, you can use the SND_RESOURCE flag instead.
Microsoft has a Knowledge Base article telling you how you can play sound from memory using MCI. You'll probably need to have the wave file header in your array, or otherwise copy in the right data during the first read, but other than that it should be fairly easy to port over.
I couldn't find a complete solution that isn't based on the outdated sndPlaySound, so here are two functions that play ".wav" files from both a TMemoryStream and from a file :
uses mmsystem;
procedure PlaySoundFromFile(FileName : String);
var
mStream : TMemoryStream;
begin
mStream := TMemoryStream.Create;
Try mStream.LoadFromFile(FileName); Except End;
If mStream.Size > 0 then PlaySoundFromStream(mStream);
mStream.Free;
end;
procedure PlaySoundFromStream(mStream : TMemoryStream);
begin
PlaySound(mStream.Memory,0,SND_MEMORY or SND_SYNC);
end;
The sound is played synchronously and from memory, you can find the other PlaySound flags in the link on Remy's answer. If you switch to async playback, make sure to not clear the sound memory before playback ends.

Resources