2013-03-07 23:43:21 +04:00
|
|
|
/**
|
|
|
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
|
|
|
* Audio Formats
|
|
|
|
*
|
|
|
|
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-02-16 13:20:38 +03:00
|
|
|
#include <freerdp/config.h>
|
2013-03-07 23:43:21 +04:00
|
|
|
|
|
|
|
#include <winpr/crt.h>
|
|
|
|
|
2014-09-12 16:36:29 +04:00
|
|
|
#include <freerdp/log.h>
|
2013-03-07 23:43:21 +04:00
|
|
|
#include <freerdp/codec/audio.h>
|
|
|
|
|
2014-09-12 16:36:29 +04:00
|
|
|
#define TAG FREERDP_TAG("codec")
|
|
|
|
|
2018-09-25 13:04:10 +03:00
|
|
|
UINT32 audio_format_compute_time_length(const AUDIO_FORMAT* format, size_t size)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
2023-07-27 11:29:13 +03:00
|
|
|
UINT32 mstime = 0;
|
|
|
|
UINT32 wSamples = 0;
|
2013-03-07 23:43:21 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* [MSDN-AUDIOFORMAT]:
|
|
|
|
* http://msdn.microsoft.com/en-us/library/ms713497.aspx
|
|
|
|
*/
|
|
|
|
|
2013-11-22 21:11:39 +04:00
|
|
|
if (format->wBitsPerSample)
|
|
|
|
{
|
2023-07-27 11:29:13 +03:00
|
|
|
const size_t samples = (size * 8) / format->wBitsPerSample;
|
|
|
|
WINPR_ASSERT(samples <= UINT32_MAX);
|
|
|
|
wSamples = (UINT32)samples;
|
2013-11-22 21:11:39 +04:00
|
|
|
mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mstime = 0;
|
|
|
|
|
|
|
|
if (format->wFormatTag == WAVE_FORMAT_GSM610)
|
|
|
|
{
|
2024-01-23 18:49:54 +03:00
|
|
|
UINT16 nSamplesPerBlock = 0;
|
2013-11-22 21:11:39 +04:00
|
|
|
|
|
|
|
if ((format->cbSize == 2) && (format->data))
|
|
|
|
{
|
2019-11-06 17:24:51 +03:00
|
|
|
nSamplesPerBlock = *((UINT16*)format->data);
|
2023-07-27 11:29:13 +03:00
|
|
|
const size_t samples = (size / format->nBlockAlign) * nSamplesPerBlock;
|
|
|
|
WINPR_ASSERT(samples <= UINT32_MAX);
|
|
|
|
wSamples = (UINT32)samples;
|
2013-11-22 21:11:39 +04:00
|
|
|
mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-11-06 17:24:51 +03:00
|
|
|
WLog_ERR(TAG,
|
|
|
|
"audio_format_compute_time_length: invalid WAVE_FORMAT_GSM610 format");
|
2013-11-22 21:11:39 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-11-06 17:24:51 +03:00
|
|
|
WLog_ERR(TAG, "audio_format_compute_time_length: unknown format %" PRIu16 "",
|
|
|
|
format->wFormatTag);
|
2013-11-22 21:11:39 +04:00
|
|
|
}
|
|
|
|
}
|
2013-03-07 23:43:21 +04:00
|
|
|
|
|
|
|
return mstime;
|
|
|
|
}
|
|
|
|
|
2024-09-12 18:07:16 +03:00
|
|
|
const char* audio_format_get_tag_string(UINT16 wFormatTag)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
|
|
|
switch (wFormatTag)
|
|
|
|
{
|
|
|
|
case WAVE_FORMAT_PCM:
|
|
|
|
return "WAVE_FORMAT_PCM";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_ADPCM:
|
|
|
|
return "WAVE_FORMAT_ADPCM";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_ALAW:
|
|
|
|
return "WAVE_FORMAT_ALAW";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_MULAW:
|
|
|
|
return "WAVE_FORMAT_MULAW";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_DVI_ADPCM:
|
|
|
|
return "WAVE_FORMAT_DVI_ADPCM";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_GSM610:
|
|
|
|
return "WAVE_FORMAT_GSM610";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_MSG723:
|
|
|
|
return "WAVE_FORMAT_MSG723";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
|
|
|
|
return "WAVE_FORMAT_DSPGROUP_TRUESPEECH ";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_MPEGLAYER3:
|
|
|
|
return "WAVE_FORMAT_MPEGLAYER3";
|
|
|
|
|
|
|
|
case WAVE_FORMAT_WMAUDIO2:
|
|
|
|
return "WAVE_FORMAT_WMAUDIO2";
|
2015-11-12 18:04:31 +03:00
|
|
|
|
|
|
|
case WAVE_FORMAT_AAC_MS:
|
|
|
|
return "WAVE_FORMAT_AAC_MS";
|
2013-03-07 23:43:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return "WAVE_FORMAT_UNKNOWN";
|
|
|
|
}
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
void audio_format_print(wLog* log, DWORD level, const AUDIO_FORMAT* format)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
2018-09-25 15:34:14 +03:00
|
|
|
WLog_Print(log, level,
|
2019-11-06 17:24:51 +03:00
|
|
|
"%s:\t wFormatTag: 0x%04" PRIX16 " nChannels: %" PRIu16 " nSamplesPerSec: %" PRIu32
|
|
|
|
" "
|
|
|
|
"nAvgBytesPerSec: %" PRIu32 " nBlockAlign: %" PRIu16 " wBitsPerSample: %" PRIu16
|
|
|
|
" cbSize: %" PRIu16 "",
|
2018-09-25 15:34:14 +03:00
|
|
|
audio_format_get_tag_string(format->wFormatTag), format->wFormatTag,
|
|
|
|
format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec,
|
|
|
|
format->nBlockAlign, format->wBitsPerSample, format->cbSize);
|
2013-03-07 23:43:21 +04:00
|
|
|
}
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
void audio_formats_print(wLog* log, DWORD level, const AUDIO_FORMAT* formats, UINT16 count)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
|
|
|
if (formats)
|
|
|
|
{
|
2019-11-06 17:24:51 +03:00
|
|
|
WLog_Print(log, level, "AUDIO_FORMATS (%" PRIu16 ") ={", count);
|
2013-03-07 23:43:21 +04:00
|
|
|
|
2024-01-30 12:25:38 +03:00
|
|
|
for (UINT32 index = 0; index < count; index++)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
2024-01-30 12:25:38 +03:00
|
|
|
const AUDIO_FORMAT* format = &formats[index];
|
2019-11-06 17:24:51 +03:00
|
|
|
WLog_Print(log, level, "\t");
|
2018-09-25 15:34:14 +03:00
|
|
|
audio_format_print(log, level, format);
|
2013-03-07 23:43:21 +04:00
|
|
|
}
|
|
|
|
|
2019-11-06 17:24:51 +03:00
|
|
|
WLog_Print(log, level, "}");
|
2013-03-07 23:43:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 13:04:10 +03:00
|
|
|
BOOL audio_format_read(wStream* s, AUDIO_FORMAT* format)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
2018-09-25 13:04:10 +03:00
|
|
|
if (!s || !format)
|
|
|
|
return FALSE;
|
|
|
|
|
2022-04-19 15:29:17 +03:00
|
|
|
if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
Stream_Read_UINT16(s, format->wFormatTag);
|
|
|
|
Stream_Read_UINT16(s, format->nChannels);
|
|
|
|
Stream_Read_UINT32(s, format->nSamplesPerSec);
|
|
|
|
Stream_Read_UINT32(s, format->nAvgBytesPerSec);
|
|
|
|
Stream_Read_UINT16(s, format->nBlockAlign);
|
|
|
|
Stream_Read_UINT16(s, format->wBitsPerSample);
|
|
|
|
Stream_Read_UINT16(s, format->cbSize);
|
|
|
|
|
2022-04-19 15:29:17 +03:00
|
|
|
if (!Stream_CheckAndLogRequiredLength(TAG, s, format->cbSize))
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
format->data = NULL;
|
|
|
|
|
|
|
|
if (format->cbSize > 0)
|
|
|
|
{
|
|
|
|
format->data = malloc(format->cbSize);
|
|
|
|
|
|
|
|
if (!format->data)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
Stream_Read(s, format->data, format->cbSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL audio_format_write(wStream* s, const AUDIO_FORMAT* format)
|
|
|
|
{
|
|
|
|
if (!s || !format)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (!Stream_EnsureRemainingCapacity(s, 18 + format->cbSize))
|
|
|
|
return FALSE;
|
|
|
|
|
2019-11-06 17:24:51 +03:00
|
|
|
Stream_Write_UINT16(s, format->wFormatTag); /* wFormatTag (WAVE_FORMAT_PCM) */
|
|
|
|
Stream_Write_UINT16(s, format->nChannels); /* nChannels */
|
|
|
|
Stream_Write_UINT32(s, format->nSamplesPerSec); /* nSamplesPerSec */
|
2018-09-25 13:04:10 +03:00
|
|
|
Stream_Write_UINT32(s, format->nAvgBytesPerSec); /* nAvgBytesPerSec */
|
2019-11-06 17:24:51 +03:00
|
|
|
Stream_Write_UINT16(s, format->nBlockAlign); /* nBlockAlign */
|
|
|
|
Stream_Write_UINT16(s, format->wBitsPerSample); /* wBitsPerSample */
|
|
|
|
Stream_Write_UINT16(s, format->cbSize); /* cbSize */
|
2018-09-25 13:04:10 +03:00
|
|
|
|
|
|
|
if (format->cbSize > 0)
|
|
|
|
Stream_Write(s, format->data, format->cbSize);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2024-05-30 14:26:04 +03:00
|
|
|
BOOL audio_format_copy(const AUDIO_FORMAT* WINPR_RESTRICT srcFormat,
|
|
|
|
AUDIO_FORMAT* WINPR_RESTRICT dstFormat)
|
2018-09-25 13:04:10 +03:00
|
|
|
{
|
|
|
|
if (!srcFormat || !dstFormat)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*dstFormat = *srcFormat;
|
|
|
|
|
|
|
|
if (srcFormat->cbSize > 0)
|
|
|
|
{
|
|
|
|
dstFormat->data = malloc(srcFormat->cbSize);
|
|
|
|
|
|
|
|
if (!dstFormat->data)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
memcpy(dstFormat->data, srcFormat->data, dstFormat->cbSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
BOOL audio_format_compatible(const AUDIO_FORMAT* with, const AUDIO_FORMAT* what)
|
2018-09-25 13:04:10 +03:00
|
|
|
{
|
2018-09-25 15:34:14 +03:00
|
|
|
if (!with || !what)
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
if (with->wFormatTag != WAVE_FORMAT_UNKNOWN)
|
|
|
|
{
|
|
|
|
if (with->wFormatTag != what->wFormatTag)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (with->nChannels != 0)
|
|
|
|
{
|
|
|
|
if (with->nChannels != what->nChannels)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (with->nSamplesPerSec != 0)
|
|
|
|
{
|
|
|
|
if (with->nSamplesPerSec != what->nSamplesPerSec)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (with->wBitsPerSample != 0)
|
|
|
|
{
|
|
|
|
if (with->wBitsPerSample != what->wBitsPerSample)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2019-11-20 13:30:14 +03:00
|
|
|
static BOOL audio_format_valid(const AUDIO_FORMAT* format)
|
2018-09-25 15:34:14 +03:00
|
|
|
{
|
|
|
|
if (!format)
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
if (format->nChannels == 0)
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
if (format->nSamplesPerSec == 0)
|
2018-09-25 13:04:10 +03:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2018-09-25 15:34:14 +03:00
|
|
|
AUDIO_FORMAT* audio_format_new(void)
|
|
|
|
{
|
|
|
|
return audio_formats_new(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
AUDIO_FORMAT* audio_formats_new(size_t count)
|
|
|
|
{
|
|
|
|
return calloc(count, sizeof(AUDIO_FORMAT));
|
|
|
|
}
|
|
|
|
|
|
|
|
void audio_format_free(AUDIO_FORMAT* format)
|
2018-09-25 13:04:10 +03:00
|
|
|
{
|
|
|
|
if (format)
|
|
|
|
free(format->data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void audio_formats_free(AUDIO_FORMAT* formats, size_t count)
|
|
|
|
{
|
2013-03-07 23:43:21 +04:00
|
|
|
if (formats)
|
|
|
|
{
|
2024-01-30 12:25:38 +03:00
|
|
|
for (size_t index = 0; index < count; index++)
|
2013-03-07 23:43:21 +04:00
|
|
|
{
|
2018-02-20 14:15:30 +03:00
|
|
|
AUDIO_FORMAT* format = &formats[index];
|
2018-09-25 15:34:14 +03:00
|
|
|
audio_format_free(format);
|
2013-03-07 23:43:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
free(formats);
|
|
|
|
}
|
|
|
|
}
|