php - 是否可以使用 PHP 生成 WAV 文件?
问题描述
我想编写一个生成短音频文件(最好是 WAV)的小型 Web 应用程序。
音频文件将非常简单,如下所示:
例如。用户输入:
350 1000
500 1000
输出:两秒 WAV 文件,第一秒是 350Hz 音调,第二秒是 500Hz 音调,就像在这个发生器中一样。
是否可以使用PHP来做到这一点?
解决方案
WAV 文件格式非常简单,您可以使用 PHP 从头开始编写 WAV 文件,前提是您只想输出可以通过编程轻松生成的简单波形。
这是一个使用您发布的输入格式将正弦波写入 8 位 44.1kHz 文件的示例。我使用以下内容作为参考:
一个简单的 C# Wave 编辑器,第 1 部分:背景和分析
<?php
/*
* Set some input - format is [Hz, milliseconds], so [440, 1000] is 440Hz (A4) for 1 second
*/
$input = [
[175, 1000],
[350, 1000],
[500, 1000],
[750, 1000],
[1000, 1000]
];
//Path to output file
$filePath = 'test.wav';
//Open a handle to our file in write mode, truncate the file if it exists
$fileHandle = fopen($filePath, 'w');
// Calculate variable dependent fields
$channels = 1; //Mono
$bitDepth = 8; //8bit
$sampleRate = 44100; //CD quality
$blockAlign = ($channels * ($bitDepth/8));
$averageBytesPerSecond = $sampleRate * $blockAlign;
/*
* Header chunk
* dwFileLength will be calculated at the end, based upon the length of the audio data
*/
$header = [
'sGroupID' => 'RIFF',
'dwFileLength' => 0,
'sRiffType' => 'WAVE'
];
/*
* Format chunk
*/
$fmtChunk = [
'sGroupID' => 'fmt',
'dwChunkSize' => 16,
'wFormatTag' => 1,
'wChannels' => $channels,
'dwSamplesPerSec' => $sampleRate,
'dwAvgBytesPerSec' => $averageBytesPerSecond,
'wBlockAlign' => $blockAlign,
'dwBitsPerSample' => $bitDepth
];
/*
* Map all fields to pack flags
* WAV format uses little-endian byte order
*/
$fieldFormatMap = [
'sGroupID' => 'A4',
'dwFileLength'=> 'V',
'sRiffType' => 'A4',
'dwChunkSize' => 'V',
'wFormatTag' => 'v',
'wChannels' => 'v',
'dwSamplesPerSec' => 'V',
'dwAvgBytesPerSec' => 'V',
'wBlockAlign' => 'v',
'dwBitsPerSample' => 'v' //Some resources say this is a uint but it's not - stay frosty.
];
/*
* Pack and write our values
* Keep track of how many bytes we write so we can update the dwFileLength in the header
*/
$dwFileLength = 0;
foreach($header as $currKey=>$currValue)
{
if(!array_key_exists($currKey, $fieldFormatMap))
{
die('Unrecognized field '.$currKey);
}
$currPackFlag = $fieldFormatMap[$currKey];
$currOutput = pack($currPackFlag, $currValue);
$dwFileLength += fwrite($fileHandle, $currOutput);
}
foreach($fmtChunk as $currKey=>$currValue)
{
if(!array_key_exists($currKey, $fieldFormatMap))
{
die('Unrecognized field '.$currKey);
}
$currPackFlag = $fieldFormatMap[$currKey];
$currOutput = pack($currPackFlag, $currValue);
$dwFileLength += fwrite($fileHandle, $currOutput);
}
/*
* Set up our data chunk
* As we write data, the dwChunkSize in this struct will be updated, be sure to pack and overwrite
* after audio data has been written
*/
$dataChunk = [
'sGroupID' => 'data',
'dwChunkSize' => 0
];
//Write sGroupID
$dwFileLength += fwrite($fileHandle, pack($fieldFormatMap['sGroupID'], $dataChunk['sGroupID']));
//Save a reference to the position in the file of the dwChunkSize field so we can overwrite later
$dataChunkSizePosition = $dwFileLength;
//Write our empty dwChunkSize field
$dwFileLength += fwrite($fileHandle, pack($fieldFormatMap['dwChunkSize'], $dataChunk['dwChunkSize']));
/*
8-bit audio: -128 to 127 (because of 2’s complement)
*/
$maxAmplitude = 127;
//Loop through input
foreach($input as $currNote)
{
$currHz = $currNote[0];
$currMillis = $currNote[1];
/*
* Each "tick" should be 1 second divided by our sample rate. Since we're counting in milliseconds, use
* 1000/$sampleRate
*/
$timeIncrement = 1000/$sampleRate;
/*
* Define how much each tick should advance the sine function. 360deg/(sample rate/frequency)
*/
$waveIncrement = 360/($sampleRate/$currHz);
/*
* Run the sine function until we have written all the samples to fill the current note time
*/
$elapsed = 0;
$x = 0;
while($elapsed<$currMillis)
{
/*
* The sine wave math
* $maxAmplitude*.95 lowers the output a bit so we're not right up at 0db
*/
$currAmplitude = ($maxAmplitude)-number_format(sin(deg2rad($x))*($maxAmplitude*.95));
//Increment our position in the wave
$x+=$waveIncrement;
//Write the sample and increment our byte counts
$currBytesWritten = fwrite($fileHandle, pack('c', $currAmplitude));
$dataChunk['dwChunkSize'] += $currBytesWritten;
$dwFileLength += $currBytesWritten;
//Update the time counter
$elapsed += $timeIncrement;
}
}
/*
* Seek to our dwFileLength and overwrite it with our final value. Make sure to subtract 8 for the
* sGroupID and sRiffType fields in the header.
*/
fseek($fileHandle, 4);
fwrite($fileHandle, pack($fieldFormatMap['dwFileLength'], ($dwFileLength-8)));
//Seek to our dwChunkSize and overwrite it with our final value
fseek($fileHandle, $dataChunkSizePosition);
fwrite($fileHandle, pack($fieldFormatMap['dwChunkSize'], $dataChunk['dwChunkSize']));
fclose($fileHandle);
这是一个概念证明,说明了如何根据您的输入创建文件,使其在 Web 应用程序中工作取决于您。
推荐阅读
- mysql - 在视图中添加列 - 使用 MySQL 低于 v8 的最后匹配条件
- php - 如何在目录中的所有txt文件中搜索单词
- django - 由于静态文件错误,我无法部署我的 django 2.2 项目
- python-3.x - 用于通过浏览器从仪器内存下载数据的 Python 脚本
- jquery - 如果 currentTime 大于 X,则 HTML5 视频运行事件一次
- c# - API 在评论属性中返回 null
- postgresql - 从 Python 更新 SQL 表
- apache-spark - 为什么 shuffle 溢出比 shuffle 读取或输出大小大得多?
- r - 有没有办法补充数据框中缺失的数字?
- typescript - 为什么打字稿省略/选择擦除符号