我正在尝试在网页上使用MediaRecorder Javascript 库录制 .mp3 和 .wav,并使用ESP32-audioI2S库在 ESP32 上播放。我可以使用 Audacity 播放器/Windows 播放器在 PC 上播放录制的 MP3 和 WAV 文件。但是 ESP32 没有使用库播放相同的 MP3/WAV。我得到WAV 文件缺少 RIFF 标头和 MP3 文件缺少标头。我附上了用于使用 MediaRecorder 进行录制的代码、用于播放录制和下载的音频文件的代码以及我收到的错误消息。用于录制音频的代码有什么问题吗?还是我在图书馆里缺少什么?在此先感谢您的帮助。
使用的板子:ESP32 开发模块
UDA1334A(Adafruit I2S 立体声解码器分线板)用于播放音频。
代码:
- 用于播放音频(相当简单)
#include <Audio.h>
#include <WiFi.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
#define I2S_DOUT 25 // connect to DAC pin DIN
#define I2S_BCLK 27 // connect to DAC pin BCK
#define I2S_LRC 26 // connect to DAC pin LCK
#define SD_CS 5
Audio audio;
void setup() {
Serial.begin(115200);
// initializing sd card
if (!SD.begin(SD_CS)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(21);
//audio.connecttoFS(SD, "test16k.wav"); // 16khz sample rate; playing fine; recorded in audacity
//audio.connecttoFS(SD, "teststd.mp3"); // 16khz sample rate; playing fine; recorded in audacity
audio.connecttoFS(SD, "sampleMp3.mp3"); // recorded using mediaRecorder - not working
//audio.connecttoFS(SD, "sampleWav.wav"); // recorded using mediaRecorder - not working
}
void loop() {
audio.loop();
}
void audio_info(const char *info){
Serial.print("info "); Serial.println(info);
}
- 用于录制 - 公开的 js、html 和 css 文件(可能只有 js 文件就可以了)
// index.js file
const mediaSelector = document.getElementById("media");
const webCamContainer =
document.getElementById("web-cam-container");
let selectedMedia = null;
// This array stores the recorded media data
let chunks = [];
// Handler function to handle the "change" event
// when the user selects some option
mediaSelector.addEventListener("change", (e) => {
// Takes the current value of the mediaSeletor
selectedMedia = e.target.value;
document.getElementById(
`${selectedMedia}-recorder`)
.style.display = "block";
document.getElementById(
`${otherRecorderContainer(
selectedMedia)}-recorder`)
.style.display = "none";
});
function otherRecorderContainer(
selectedMedia) {
return selectedMedia === "vid" ?
"aud" : "vid";
}
// This constraints object tells
// the browser to include only
// the audio Media Track
const audioMediaConstraints = {
audio: true,
video: false,
};
// This constraints object tells
// the browser to include
// both the audio and video
// Media Tracks
const videoMediaConstraints = {
// or you can set audio to
// false to record
// only video
audio: true,
video: true,
};
// When the user clicks the "Start
// Recording" button this function
// gets invoked
function startRecording(
thisButton, otherButton) {
// Access the camera and microphone
navigator.mediaDevices.getUserMedia(
selectedMedia === "vid" ?
videoMediaConstraints :
audioMediaConstraints)
.then((mediaStream) => {
// Create a new MediaRecorder instance
const mediaRecorder =
// new MediaRecorder(mediaStream,options);
new MediaRecorder(mediaStream);
//Make the mediaStream global
window.mediaStream = mediaStream;
//Make the mediaRecorder global
window.mediaRecorder = mediaRecorder;
mediaRecorder.start();
// Whenever (here when the recorder
// stops recording) data is available
// the MediaRecorder emits a "dataavailable"
// event with the recorded media data.
mediaRecorder.ondataavailable = (e) => {
// Push the recorded media data to
// the chunks array
chunks.push(e.data);
};
// When the MediaRecorder stops
// recording, it emits "stop"
// event
mediaRecorder.onstop = () => {
/* A Blob is a File like object.
In fact, the File interface is
based on Blob. File inherits the
Blob interface and expands it to
support the files on the user's
systemThe Blob constructor takes
the chunk of media data as the
first parameter and constructs
a Blob of the type given as the
second parameter*/
const blob = new Blob(
chunks, {
type: selectedMedia === "vid" ?
"video/mp4" : "audio/mp3" // audio/wav for wav file
});
chunks = [];
// Create a video or audio element
// that stores the recorded media
const recordedMedia = document.createElement(
selectedMedia === "vid" ? "video" : "audio");
recordedMedia.controls = true;
// You can not directly set the blob as
// the source of the video or audio element
// Instead, you need to create a URL for blob
// using URL.createObjectURL() method.
const recordedMediaURL = URL.createObjectURL(blob);
// Now you can use the created URL as the
// source of the video or audio element
recordedMedia.src = recordedMediaURL;
// Create a download button that lets the
// user download the recorded media
const downloadButton = document.createElement("a");
// Set the download attribute to true so that
// when the user clicks the link the recorded
// media is automatically gets downloaded.
downloadButton.download = "Recorded-Media";
downloadButton.href = recordedMediaURL;
downloadButton.innerText = "Download it!";
downloadButton.onclick = () => {
/* After download revoke the created URL
using URL.revokeObjectURL() method to
avoid possible memory leak. Though,
the browser automatically revokes the
created URL when the document is unloaded,
but still it is good to revoke the created
URLs */
URL.revokeObjectURL(recordedMedia);
};
document.getElementById(
`${selectedMedia}-recorder`).append(
recordedMedia, downloadButton);
};
if (selectedMedia === "vid") {
// Remember to use the srcObject
// attribute since the src attribute
// doesn't support media stream as a value
webCamContainer.srcObject = mediaStream;
}
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording";
thisButton.disabled = true;
otherButton.disabled = false;
});
}
function stopRecording(thisButton, otherButton) {
// Stop the recording
window.mediaRecorder.stop();
// Stop all the tracks in the
// received media stream
window.mediaStream.getTracks()
.forEach((track) => {
track.stop();
});
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording done!";
thisButton.disabled = true;
otherButton.disabled = false;
}
//index.css file (just in case you need it)
body {
text-align: center;
color: green;
font-size: 1.2em;
}
.display-none {
display: none;
}
.recording {
color: red;
background-color: rgb(241 211 211);
padding: 5px;
margin: 6px auto;
width: fit-content;
}
video {
background-color: black;
display: block;
margin: 6px auto;
width: 420px;
height: 240px;
}
audio {
display: block;
margin: 6px auto;
}
a {
color: green;
}
//index.html file (just in case you need it)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="index.css">
<title>Video & Audio Recorder</title>
</head>
<body>
<h1> Video & Audio Recorder </h1>
<label for="media">
Select what you want to record:
</label>
<select id="media">
<option value="choose-an-option">
Choose an option
</option>
<option value="vid">Video</option>
<option value="aud">Audio</option>
</select>
<div class="display-none" id="vid-recorder">
<h3>Record Video </h3>
<video autoplay id="web-cam-container" style="background-color: black;">
Your browser doesn't support
the video tag
</video>
<div class="recording" id="vid-record-status">
Click the "Start video Recording"
button to start recording
</div>
<!-- This button will start the video recording -->
<button type="button" id="start-vid-recording" onclick="startRecording(this,
document.getElementById('stop-vid-recording'))">
Start video recording
</button>
<!-- This button will stop the video recording -->
<button type="button" id="stop-vid-recording" disabled onclick="stopRecording(this,
document.getElementById('start-vid-recording'))">
Stop video recording
</button>
<!--The video element will be created using
JavaScript and contains recorded video-->
<!-- <video id="recorded-video" controls>
Your browser doesn't support the video tag
</video> -->
<!-- The below link will let the
users download the recorded video -->
<!-- <a href="" > Download it! </a> -->
</div>
<div class="display-none" id="aud-recorder">
<h3> Record Audio</h3>
<div class="recording" id="aud-record-status">
Click the "Start Recording"
button to start recording
</div>
<button type="button" id="start-aud-recording" onclick="startRecording(this,
document.getElementById('stop-aud-recording'))">
Start recording
</button>
<button type="button" id="stop-aud-recording" disabled onclick="stopRecording(this,
document.getElementById('start-aud-recording'))">
Stop recording
</button>
<!-- The audio element will contain the
recorded audio and will be created
using Javascript -->
<!-- <audio id="recorded-audio"
controls></audio> -->
<!-- The below link will let the users
download the recorded audio -->
<!-- <a href="" > Download it! </a> -->
</div>
<script src="index.js"></script>
</body>
</html>
错误信息:
wav 文件:
12:25:03.750 -> info PSRAM not found, inputBufferSize: 6399 bytes
12:25:03.750 -> info buffers freed, free Heap: 286876 bytes
12:25:03.750 -> info Reading file: "/sampleWav.wav"
12:25:03.797 -> info stream ready
12:25:03.797 -> info file has no RIFF tag
12:25:03.797 -> info syncword found at pos 0
12:25:03.797 -> Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero). Exception was unhandled.
12:25:03.797 -> Core 1 register dump:
12:25:03.797 -> PC : 0x400d7423 PS : 0x00060530 A0 : 0x800d78ec A1 : 0x3ffb1f00
12:25:03.797 -> A2 : 0x3ffbff28 A3 : 0x3ffc2328 A4 : 0x00000640 A5 : 0x3ffc2428
12:25:03.844 -> A6 : 0x3ffc0328 A7 : 0xff000000 A8 : 0x00000000 A9 : 0x3ffb1ee0
12:25:03.844 -> A10 : 0x3ffc03f6 A11 : 0x3ffb2068 A12 : 0x00000640 A13 : 0x00000000
12:25:03.844 -> A14 : 0x3ffc0328 A15 : 0x3ffb889c SAR : 0x0000001b EXCCAUSE: 0x00000006
12:25:03.844 -> EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff
12:25:03.844 ->
12:25:03.844 -> ELF file SHA256: 0000000000000000
12:25:03.844 ->
12:25:03.844 -> Backtrace: 0x400d7423:0x3ffb1f00 0x400d78e9:0x3ffb1f40 0x400da6e1:0x3ffb1f70 0x400d1846:0x3ffb1f90 0x400ee7b1:0x3ffb1fb0 0x400865de:0x3ffb1fd0
12:25:03.892 ->
12:25:03.892 -> Rebooting...
12:25:04.141 -> info PSRAM not found, inputBufferSize: 6399 bytes
12:25:04.141 -> info buffers freed, free Heap: 286876 bytes
12:25:04.141 -> info Reading file: "/sampleWav.wav"
12:25:04.188 -> info stream ready
12:25:04.188 -> info file has no RIFF tag
12:25:04.188 -> info syncword found at pos 0
12:25:04.188 -> Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero). Exception was unhandled.
12:25:04.188 -> Core 1 register dump:
12:25:04.188 -> PC : 0x400d7423 PS : 0x00060530 A0 : 0x800d78ec A1 : 0x3ffb1f00
12:25:04.235 -> A2 : 0x3ffbff28 A3 : 0x3ffc2328 A4 : 0x00000640 A5 : 0x3ffc2428
12:25:04.235 -> A6 : 0x3ffc0328 A7 : 0xff000000 A8 : 0x00000000 A9 : 0x3ffb1ee0
12:25:04.235 -> A10 : 0x3ffc03f6 A11 : 0x3ffb2068 A12 : 0x00000640 A13 : 0x00000000
12:25:04.235 -> A14 : 0x3ffc0328 A15 : 0x3ffb889c SAR : 0x0000001b EXCCAUSE: 0x00000006
12:25:04.235 -> EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff
12:25:04.235 ->
12:25:04.235 -> ELF file SHA256: 0000000000000000
mp3 文件:
12:51:19.860 -> info PSRAM not found, inputBufferSize: 6399 bytes
12:51:19.860 -> info buffers freed, free Heap: 286876 bytes
12:51:19.860 -> info Reading file: "/sampleMp3.mp3"
12:51:19.860 -> info MP3Decoder has been initialized, free Heap: 262912 bytes
12:51:19.907 -> info stream ready
12:51:19.907 -> info Content-Length: 85414
12:51:19.907 -> info file has no mp3 tag, skip metadata
12:51:19.907 -> info Audio-Length: 85414
12:51:19.907 -> info syncword found at pos 41
12:51:19.907 -> info syncword found at pos 0
12:51:19.907 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.907 -> info syncword found at pos 0
12:51:19.907 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.907 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword found at pos 104
12:51:19.969 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword found at pos 15
12:51:19.969 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword not found
12:51:19.969 -> info syncword found at pos 1405
12:51:19.969 -> info syncword not found 10 times
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:19.969 -> info syncword found at pos 1145
12:51:19.969 -> info syncword found at pos 0
12:51:19.969 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:20.001 -> info syncword not found
12:51:20.001 -> info syncword found at pos 1591
12:51:20.001 -> info syncword not found 21 times
12:51:20.001 -> info MP3 decode error -9 : INVALID_HUFFCODES
12:51:20.001 -> info syncword not found
12:51:20.047 -> info syncword found at pos 1411
12:51:20.047 -> info syncword not found 17 times
12:51:20.047 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:20.047 -> info syncword not found
12:51:20.047 -> info syncword found at pos 1495
12:51:20.095 -> info syncword not found 31 times
12:51:20.095 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:20.095 -> info syncword found at pos 178
12:51:20.095 -> info syncword found at pos 0
12:51:20.095 -> info MP3 decode error -6 : INVALID_FRAMEHEADER
12:51:20.095 -> info syncword not found
12:51:20.141 -> info syncword found at pos 1460
12:51:20.141 -> info syncword not found 49 times
12:51:20.141 -> E (326) I2S: clkmdiv is too large
12:51:20.141 ->
12:51:20.141 -> info Channels: 0
12:51:20.141 -> info SampleRate: 0
12:51:20.141 -> info BitsPerSample: 16
12:51:20.141 -> info BitRate: N/A
12:51:20.141 -> Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero). Exception was unhandled.
12:51:20.141 -> Core 1 register dump:
12:51:20.141 -> PC : 0x400d7603 PS : 0x00060d30 A0 : 0x800d78ec A1 : 0x3ffb1f00
12:51:20.188 -> A2 : 0x3ffbff28 A3 : 0x3ffc2328 A4 : 0x000000a8 A5 : 0x00000000
12:51:20.188 -> A6 : 0x3ffc0328 A7 : 0x3b245260 A8 : 0x800d7600 A9 : 0x3ffb1ee0
12:51:20.188 -> A10 : 0x00000000 A11 : 0x3ffc0328 A12 : 0x400d184c A13 : 0x00000000
12:51:20.188 -> A14 : 0x3ffc0328 A15 : 0x00000000 SAR : 0x00000004 EXCCAUSE: 0x00000006
12:51:20.188 -> EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff
12:51:20.188 ->
12:51:20.188 -> ELF file SHA256: 0000000000000000
12:51:20.188 ->
12:51:20.188 -> Backtrace: 0x400d7603:0x3ffb1f00 0x400d78e9:0x3ffb1f40 0x400da6e1:0x3ffb1f70 0x400d1846:0x3ffb1f90 0x400ee7b1:0x3ffb1fb0 0x400865de:0x3ffb1fd0
12:51:20.235 ->
12:51:20.235 -> Rebooting...