Winapi запись с микрофона в Windows

  • Автор темы Firefox
  • Дата начала
F

Firefox

Добрый день. мне необходимо записать звук с микрофона в какой-либо буфер, а из буфера в файл.
пыталась сделать следующее, но в буфер информация не заносится, мне так кажется, потому что файл пустой записывается.
Пишу на С++ с использованием библиотеки qt4.3.3, есть один из вариантов решения использования Phonon - класс QAudioInput , но он внедрен в qt начиная с версии 4.5, установить её не имею возможности(на работе все строго). Может сдесь кто-то поможет. вот вариант проги, который я написала, может сдесь найдутся люди которые помогут.
C++:
// срр файл

#include "sound_2.h"
using namespace std; 
int fact;
#define N2 44100
//WAVEFORMATEX pcmWaveFormat; 
DWORD r_buff[1024];
WAVEHDR whdr;
HWAVEIN h_out;
HMMIO H_IN;
bool Param_record=0;
MMCKINFO	  ckOutRIFF; 
MMCKINFO	  ckOut;	 
PCMWAVEFORMAT pcmWaveFormat; 
MMIOINFO	  mmioinfoOut;  
long		 lSamples; // number of samples 

sound_2::sound_2(QWidget *parent, Qt::WFlags flags)
: QWidget(parent, flags)
{
ui.setupUi(this);
CreateWaveFile();
Tim=new QTimer(this);
connect(Tim,SIGNAL(timeout()),SLOT(WriteDataToFile()));
Tim->start(200);

}

sound_2::~sound_2()
{

}
void sound_2::func(bool param)
{
MMRESULT MRez;
MRez=waveInOpen( (LPHWAVEIN)&h_out, WAVE_MAPPER,(LPCWAVEFORMATEX)&pcmWaveFormat, NULL,  NULL, CALLBACK_THREAD);
char* lpDst;
ZeroMemory(&whdr,sizeof(whdr));
lpDst=new char[1024];
whdr.lpData=(LPSTR)lpDst;
whdr.dwFlags= WHDR_BEGINLOOP;
for(int i=0;i<3;i++)
{
waveInPrepareHeader (h_out,&whdr,sizeof(whdr));// подготовка буферов в которые будет записываться информация с микрофона
waveInAddBuffer(h_out,&whdr,sizeof(whdr));	
}
//	H_IN=mmioOpen(L"Sound1.wav",NULL,MMIO_CREATE|MMIO_WRITE|MMIO_ALLOCBUF );
//mmioWrite(H_IN,lpDst,sizeof(lpDst));
//waveInUnprepareHeader(h_out,&whdr,sizeof(whdr)); 
//	mmioClose(H_IN,NULL);
}
void sound_2::WriteDataToFile()
{
if (Param_record==1)
{
waveInUnprepareHeader(h_out,&whdr,sizeof(whdr)); 
writeWaveFile();
waveInPrepareHeader (h_out,&whdr,sizeof(whdr));
waveInAddBuffer(h_out,&whdr,sizeof(whdr));	
}
}
void sound_2::CreateWaveFile()// Создание контейнеров звукового файла
{
H_IN=mmioOpen(L"Sound1.wav",NULL,MMIO_CREATE|MMIO_WRITE|MMIO_ALLOCBUF );
// Write the PCMWAVEFORMAT structure 
//----------------------------------------------
pcmWaveFormat.wf.wFormatTag=WAVE_FORMAT_PCM;
pcmWaveFormat.wf.nChannels=1;
pcmWaveFormat.wf.nBlockAlign=2;
pcmWaveFormat.wf.nSamplesPerSec=N2;
pcmWaveFormat.wf.nAvgBytesPerSec=N2*2;
pcmWaveFormat.wBitsPerSample=16;

//----------------------------------------------

if (H_IN == NULL)
cout<<"error opening out file "<<endl;

// Write the PCMWAVEFORMAT structure 
ckOutRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if (mmioCreateChunk(H_IN, &ckOutRIFF, MMIO_CREATERIFF) != 0)
cout<<"error creating out file wave "<<endl;

ckOut.ckid = mmioFOURCC('f', 'm', 't', ' ');
if (mmioCreateChunk(H_IN, &ckOut, 0) != 0)
cout<<"error creating out file fmt "<<endl;


if (mmioWrite(H_IN, (HPSTR) &pcmWaveFormat, sizeof(pcmWaveFormat))
!= sizeof(pcmWaveFormat))
cout<<"error writing size of fmt "<<endl; // cannot write file, probably

if (mmioAscend(H_IN, &ckOut, 0) != 0)
cout<<"error writing size of fmt 2 "<<endl; // cannot write file, probably

ckOut.ckid = mmioFOURCC('d', 'a', 't', 'a');
if (mmioCreateChunk(H_IN, &ckOut, 0) != 0)
cout<<"error creating out file data "<<endl;
}
void sound_2::writeWaveFile()
{

mmioWrite(H_IN,whdr.lpData,sizeof(whdr.lpData));// запись информации в файл
/*for (int lSamples =2*N2; lSamples > 0; lSamples--)
{
if (mmioinfoOut.pchNext == mmioinfoOut.pchEndWrite)
{
mmioinfoOut.dwFlags |= MMIO_DIRTY;
if (mmioAdvance(hmmioOut, &mmioinfoOut, MMIO_WRITE) != 0)
cout<<"error write "<<endl;
}
*(mmioinfoOut.pchNext)++ =pDataEcho[2*N2-lSamples];
}*/

}
void sound_2::on_pushButton_clicked(bool nn)// Старт кнопка
{
if (nn==true) 
{
func(true);
waveInStart(h_out);
Param_record=1;
}
}


void sound_2::on_pushButton_2_clicked()// Стоп кнопка
{
Param_record=0;
waveInReset(h_out);
if (mmioGetInfo(H_IN, &mmioinfoOut, 0) != 0)
cout<<"error writing data in file "<<endl;
ckOut.cksize = sizeof(pcmWaveFormat)*10; 
mmioinfoOut.dwFlags |= MMIO_DIRTY;
if (mmioSetInfo(H_IN, &mmioinfoOut, 0) != 0)
cout<<"error end "<<endl;

if (mmioAscend(H_IN, &ckOut, 0) != 0)
cout<<"error end "<<endl;

if (mmioAscend(H_IN, &ckOutRIFF, 0) != 0)
cout<<"error end "<<endl;

if (H_IN != NULL)
mmioClose(H_IN, 0);
waveInClose(h_out);
}
//*********************************
// h файл

#ifndef SOUND_2_H
#define SOUND_2_H

#include <QtGui/QWidget>
#include "ui_sound_2.h"
#include "Windows.h"
#include "mmsystem.h"
#include <iostream> 
#include <QFile>
#include <QTimer>

class sound_2 : public QWidget
{
Q_OBJECT

public:
sound_2(QWidget *parent = 0, Qt::WFlags flags = 0);
~sound_2();
QTimer *Tim;
void func(bool param);
void CreateWaveFile();
void writeWaveFile();

private:
Ui::sound_2Class ui;
WAVEFORMATEX wf;

private slots:
void on_pushButton_2_clicked();
void on_pushButton_clicked(bool nn);
void WriteDataToFile();
};

#endif // SOUND_2_H
 
F

Firefox

Может кто-то поможет мне ту же задачу реализовать при помощи директсаунда?
 
L

lazybiz

Я как-то в свое время писал программу. Но тут нет сохранения в файл. В общем взгляни.
 

Вложения

  • Projects.zip
    49,8 КБ · Просмотры: 257
F

Firefox

Я как-то в свое время писал программу. Но тут нет сохранения в файл. В общем взгляни.

нашла там кусочек надо вот поразбираться теперь немного. дома студии нет к сожалению только в понедельник смогу запустить
 
L

lazybiz

Если время будет, попробую сделать запись в файл.
 
F

Firefox

Запись в файл из буфера у меня есть в теме про эхо эффект. тоесть мне кусочек записи с микрофона в буфер нужен именно.))
 
F

Firefox

Я как-то в свое время писал программу. Но тут нет сохранения в файл. В общем взгляни.
Посмотрела вашу программу. немного переделала, но записывается в файл звуковые данные не те, писк какой-то. не могли бы вы посмотреть оттуда ли я информацию считываю.
C++:
//main.h
#ifndef	__MAIN_H__
#define	__MAIN_H__
#include "mmsystem.h"
PCMWAVEFORMAT pcmWaveFormat; 
HMMIO		hmmioOut; // handle to open output WAVE file
MMCKINFO	  ckOutRIFF; 
MMCKINFO	  ckOut;	 
MMIOINFO	  mmioinfoOut;  
//char *pDataEcho;
DWORD dwDataSize;
typedef				signed char						s8;
typedef				signed short					s16;
typedef				signed long						s32;
typedef				signed long long				s64;

typedef				unsigned char					u8;
typedef				unsigned short					u16;
typedef				unsigned long					u32;
typedef				unsigned long long				u64;

typedef				float							f32;
typedef				double							f64;

#endif	// __MAIN_H__

//main.cpp

#define	INITGUID
#define	DIRECTSOUND_VERSION		0x0800

#include <windows.h>
#include <dsound.h>
#include <math.h>
#include "main.h"
#define							BUFFER_SAMPLES		(256)
#define							BUFFER_BYTES		(BUFFER_SAMPLES << 1)

#define							W					(BUFFER_SAMPLES)
#define							H					(34)
#define N2 44100
char *							g_szWindowClass		= "analogizer32";
HWND							g_hWnd;
LPDIRECTSOUNDCAPTURE8			g_pDSCapture		= NULL;
LPDIRECTSOUNDCAPTUREBUFFER8		g_pDSCaptureBuffer	= NULL;
BITMAPINFO						g_BI;
long *							g_pDIB;
HANDLE							g_hThread;
BOOL							g_bThreadExit;
WAVEFORMATEX WaveFormat = WAVEFORMATEX();
char *pDataEcho = new char[dwDataSize+int(1.5*WaveFormat.nAvgBytesPerSec)]; //Введем новую указатель на данные и увеличим его на 1.5(секунды) * Байты в секунду

typedef struct {
f32		x, y;
} x_vertex;



f32 sign( f32 x, f32 y, x_vertex *v0, x_vertex *v1 )
{
return (x - v1->x) * (v0->y - v1->y) - (v0->x - v1->x) * (y - v1->y);
}


bool createFileWav(void)
{
pcmWaveFormat.wf.wFormatTag=WAVE_FORMAT_PCM;
pcmWaveFormat.wf.nChannels=1;
pcmWaveFormat.wf.nSamplesPerSec=44100;
pcmWaveFormat.wBitsPerSample=16;
pcmWaveFormat.wf.nBlockAlign=pcmWaveFormat.wf.nChannels * (pcmWaveFormat.wBitsPerSample / 8);
pcmWaveFormat.wf.nAvgBytesPerSec=pcmWaveFormat.wf.nSamplesPerSec * pcmWaveFormat.wf.nBlockAlign;
//----------------------------------------------

LPTSTR m_FileName=L"Sound_out.wav";

hmmioOut = mmioOpen(L"Sound_out.wav", NULL,
MMIO_ALLOCBUF | MMIO_WRITE | MMIO_CREATE);
if (hmmioOut == NULL)
{
::MessageBox(NULL,L"Ошибка при открытии файла wave", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
return false;
}

// Write the PCMWAVEFORMAT structure 
ckOutRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if (mmioCreateChunk(hmmioOut, &ckOutRIFF, MMIO_CREATERIFF) != 0)
{
::MessageBox(NULL,L"Ошибка при создании сегмента wave", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}

ckOut.ckid = mmioFOURCC('f', 'm', 't', ' ');
if (mmioCreateChunk(hmmioOut, &ckOut, 0) != 0)
{
::MessageBox(NULL,L"Ошибка при создании сегмента fmt", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}


if (mmioWrite(hmmioOut, (HPSTR) &pcmWaveFormat, sizeof(pcmWaveFormat))
!= sizeof(pcmWaveFormat))
{
::MessageBox(NULL,L"Ошибка при создании сегмента fmt и записи его содержмого", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}

if (mmioAscend(hmmioOut, &ckOut, 0) != 0)
{
::MessageBox(NULL,L"Ошибка при зактытии сегмента fmt", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}

ckOut.ckid = mmioFOURCC('d', 'a', 't', 'a');
if (mmioCreateChunk(hmmioOut, &ckOut, 0) != 0)
{
::MessageBox(NULL,L"Ошибка при создании сегмента data", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}
}
void ds_init( void )
{
memset(pDataEcho,0,dwDataSize+1.5*WaveFormat.nAvgBytesPerSec); //обнулим его
DirectSoundCaptureCreate8( NULL, &g_pDSCapture, NULL );
WAVEFORMATEX	wfx;
wfx.wFormatTag		= WAVE_FORMAT_PCM;
wfx.nChannels		= 1;
wfx.nSamplesPerSec	= 44100;
wfx.wBitsPerSample	= 16;
wfx.nBlockAlign		= wfx.nChannels * (wfx.wBitsPerSample / 8);
wfx.nAvgBytesPerSec	= wfx.nSamplesPerSec * wfx.nBlockAlign;
wfx.cbSize			= 0;

DSCBUFFERDESC	dsbd;
ZeroMemory( &dsbd, sizeof( DSCBUFFERDESC ) );
dsbd.dwSize			= sizeof( DSCBUFFERDESC );
dsbd.dwBufferBytes	= BUFFER_BYTES;
dsbd.lpwfxFormat	= &wfx;
createFileWav();
LPDIRECTSOUNDCAPTUREBUFFER	pDSCB;
g_pDSCapture->CreateCaptureBuffer( &dsbd, &pDSCB, NULL );
pDSCB->QueryInterface( IID_IDirectSoundCaptureBuffer8, (LPVOID *)&g_pDSCaptureBuffer );
pDSCB->Release();

g_pDSCaptureBuffer->Start( DSCBSTART_LOOPING );
}

bool ds_close( void )
{

if (mmioAscend(hmmioOut, &ckOut, 0) != 0)
{
::MessageBox(NULL,L"Ошибка при закрытии блока данных data", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}
if (mmioAscend(hmmioOut, &ckOutRIFF, 0) != 0)
{
::MessageBox(NULL,L"Ошибка при закрытии блока RIFF", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}

if (hmmioOut != NULL)
mmioClose(hmmioOut, 0);

g_pDSCaptureBuffer->Stop();
}

void update( HDC hDC )
{
if ( !hDC ) hDC = GetDC( g_hWnd );
SetDIBitsToDevice( hDC, 0, 0, W, H, 0, 0, 0, H, g_pDIB, &g_BI, DIB_RGB_COLORS );
}

DWORD WINAPI rec_thread( LPVOID lpParam )
{
while ( !g_bThreadExit ) {
char *	p = NULL;
DWORD	s = W;

FillMemory( g_pDIB, W*H*4, 0xff );


g_pDSCaptureBuffer->Lock( 0, 0, (LPVOID *)&p, &s, NULL, NULL, DSCBLOCK_ENTIREBUFFER );

int				i, j;

for ( i = 0; i < BUFFER_SAMPLES - 1; i++ ) {

DWORD bw;
x_vertex	v0, v1;
j = i + 1;
v0.x = i;
v0.y = (p[i] >> 11) + (H >> 1);

pDataEcho[i]=p[i];
dwDataSize=s;
v1.x = j;
v1.y = (p[j] >> 11) + (H >> 1);

pDataEcho[i]=p[i];
dwDataSize=s;
}

g_pDSCaptureBuffer->Unlock( p, s, NULL, NULL );
if(mmioWrite(hmmioOut,pDataEcho,dwDataSize)!=dwDataSize)
{
::MessageBox(NULL,L"Ошибка при записи данных", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}
update( NULL );

//		Sleep( 100 );
}
return 0;
}

LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch ( msg ) {
case WM_NCHITTEST: return HTCAPTION;break;

//case WM_PAINT: {
//	PAINTSTRUCT	ps;
//	//BeginPaint( hWnd, &ps );
//	//update( ps.hdc );
//	//EndPaint( hWnd, &ps );
//} return 0;

case WM_KEYDOWN: 
{
switch( wParam ) 
{
case VK_ESCAPE:
PostQuitMessage( 0 );
break;
}
} 
break;

case WM_CLOSE:
case WM_DESTROY: {
PostQuitMessage( 0 );
} break;

default: return DefWindowProc( hWnd, msg, wParam, lParam );
}
return 0;
}

int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPInst, LPSTR lpCmdLine, int nCmdShow )
{
WNDCLASS	wc;

ZeroMemory( &wc, sizeof( WNDCLASS ) );
wc.style			= CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc		= WndProc;
wc.hInstance		= hInstance;
wc.hCursor			= LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground	= NULL; //(HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName	= (LPCWSTR)g_szWindowClass;
RegisterClass( &wc );

ZeroMemory( &g_BI, sizeof( BITMAPINFO ) );
g_BI.bmiHeader.biSize			= sizeof( BITMAPINFOHEADER );
g_BI.bmiHeader.biWidth			= W;
g_BI.bmiHeader.biHeight			= -H;
g_BI.bmiHeader.biPlanes			= 1;
g_BI.bmiHeader.biBitCount		= 32;
g_BI.bmiHeader.biCompression	= BI_RGB;
g_pDIB = new long [W * H];

g_hWnd = CreateWindowEx( WS_EX_TOPMOST,(LPCWSTR) g_szWindowClass, L"x3d", WS_POPUP | WS_VISIBLE, 100, 100, W, H, NULL, NULL, wc.hInstance, NULL );

ds_init();

g_hThread = CreateThread( NULL, 0, rec_thread, NULL, CREATE_SUSPENDED, NULL );
SetThreadPriority( g_hThread, THREAD_PRIORITY_NORMAL );
ResumeThread( g_hThread );

MSG		msg;
while ( GetMessage( &msg, NULL, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}

g_bThreadExit = TRUE;
WaitForSingleObject( g_hThread, INFINITE );
CloseHandle( g_hThread );

ds_close();
UnregisterClass((LPCWSTR) g_szWindowClass, wc.hInstance );
return 0;
}
 
L

lazybiz

Я не совсем понимаю этой строки:
char *pDataEcho = new char[dwDataSize+int(1.5*WaveFormat.nAvgBytesPerSec)]; //Введем новую указатель на данные и увеличим его на 1.5(секунды) * Байты в секунду
dwDataSize не инициализирована, да и почему бы pDataEcho просто не определить как:
C++:
char pDataEcho[BUFFER_BYTES];

Два раза одно и то же ???:
C++:
			pDataEcho[i]=p[i];
dwDataSize=s;
v1.x = j;
v1.y = (p[j] >> 11) + (H >> 1);

pDataEcho[i]=p[i];
dwDataSize=s;

The mmioWrite function writes a specified number of bytes to a file ...
Сомневаюсь что в функцию передается количество байт. Скорее передается количество сэмплов, а это уже слова.
 
F

Firefox

на счет char pDataEcho[BUFFER_BYTES]; не спорю, моя строка их файла предыдущего осталась. решила что размера хватит точно буфера и не стала исправлять. а вот насчет
C++:
 pDataEcho[i]=p[i];
dwDataSize=s;
v1.x = j;
v1.y = (p[j] >> 11) + (H >> 1);

pDataEcho[i]=p[i];
dwDataSize=s;
у тебя было 2 смещения в буфере сделано, сделала по аналогии, хотя пробовала и так
C++:
 pDataEcho[i]=p[i];
dwDataSize=s;
v1.x = j;
v1.y = (p[j] >> 11) + (H >> 1);
то есть одни раз. про количество байт замечание верное спасибо)
так правильно будет?
C++:
 for ( i = 0; i < BUFFER_SAMPLES - 1; i++ ) {

DWORD bw;
x_vertex	v0, v1;
j = i + 1;
v0.x = i;
v0.y = (p[i] >> 11) + (H >> 1);

pDataEcho[i]=p[i];
dwDataSize=s;
v1.x = j;
v1.y = (p[j] >> 11) + (H >> 1);
}

g_pDSCaptureBuffer->Unlock( p, s, NULL, NULL );
if(mmioWrite(hmmioOut,pDataEcho,sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))!=sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))
{
::MessageBox(NULL,L"Ошибка при записи данных", L"Ошибка",MB_OK|MB_SYSTEMMODAL);
::mmioClose(hmmioOut,NULL);
return false;
}
смещение в буферах при чтении самое для меня сложное для понимания.
 
L

lazybiz

так правильно будет?
Так будет правильней.

Ты давай завязывай))
C++:
if(mmioWrite(hmmioOut,pDataEcho,sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))!=sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))

Замени на:
C++:
mmioWrite( hmmioOut, pDataEcho, BUFFER_BYTES );

Если не работает, скинь мне хотя бы EXE-шник. А лучше вместе с исходником. (bizn@inbox.ru)
 
F

Firefox

Добавлено:
Так будет правильней.

Ты давай завязывай))
C++:
if(mmioWrite(hmmioOut,pDataEcho,sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))!=sizeof(pDataEcho)*( BUFFER_SAMPLES - 1))

Замени на:
C++:
mmioWrite( hmmioOut, pDataEcho, BUFFER_BYTES );

Если не работает, скинь мне хотя бы EXE-шник. А лучше вместе с исходником. (bizn@inbox.ru)
не работает. записывает всеравно шум и пищание. вот прога целиком. я бы завязала с удовольствием но сделать ооочень надо а опыта пока в программировании не так много а сдать в конце месяца полугодичный проект надо, без функции записи с микрофона не примут. а помогать мне только ты пока согласился где не спрашивала больше никто не знает и помочь не может((
 

Вложения

  • main.rar
    278,1 КБ · Просмотры: 208
L

lazybiz

Хм.. так у тебя не шум записывается. Судя по WAV-файлу у тебя записывается какая-то квадратная волна... Через другие программы звук с микрофона записывается?
И еще, тебе для записи в WAV-файл обязательно пользоваться именно функциями mmioWrite и т.п.?

Добавлено: P.S.: над кодом подумаю.
 
F

Firefox

мне как бы не важно какими средствами и функциями записывать главное чтоб болие менее стандартными, чтоб не пришлось искать библиотеку какую-то по всему инету. с микрофоном все хорошо через другие проги записывает без проблем.
 
L

lazybiz

/* Сейчас будет рабочий вариант. */

Добавил:
В скором времени не будет. У меня стала неисправна звуковая плата. Извини, даже не знаю что сказать...
 
F

Firefox

Вот такая штука странная звук записывается в файл но проблемно, с заиканиями, повторами обрезаниями и тому подобное может мне кто-то объяснить что не так перепробовала уже размер буфера менять. форматы менять становиться только хуже((


Добавлено:
Вот такая штука странная звук записывается в файл но проблемно, с заиканиями, повторами обрезаниями и тому подобное может мне кто-то объяснить что не так перепробовала уже размер буфера менять. форматы менять становиться только хуже((
 

Вложения

  • NEW_REC.rar
    749,5 КБ · Просмотры: 280
F

Firefox

Извиняюсь что поздно но может кому понадобиться итог этого топика. все записывает правильно
 

Вложения

  • my_rec.rar
    1,8 МБ · Просмотры: 369
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!