-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop Audio Nodes #1115
Open
TheMutta
wants to merge
55
commits into
v3_develop
Choose a base branch
from
v3_audio_nodes
base: v3_develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Develop Audio Nodes #1115
Changes from 43 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
216b064
Added AudioIn node.
TheMutta d9cc8cc
Added audio to CMakeLists
TheMutta 8f531f6
Removed unused functions.
TheMutta 0c8df80
Audio nodes skeleton
TheMutta ee7a202
Removed log files.
TheMutta 29ec561
Added new audio helpers with the use of libsndfile.
TheMutta ab482c4
Fixed bugs in AudioReplay and made AudioIn/Out host runnable
TheMutta 9c5ac1d
Implemented basic mixer
TheMutta 098c776
Example for audio testing updated.
TheMutta fd2bd3e
Added AudioFrame for future use.
TheMutta 7851fb6
Supporting all audio formats from 8 bit to double
TheMutta 3efa2bb
Added audio frame to cmake.
TheMutta a67e60d
Added audio encoder for format/rate
TheMutta a79ed6b
Finalized design and layout of AudioFrame datatype.
TheMutta 204d989
Usign AudioFrame in place of Buffer for audio nodes.
TheMutta f40443f
Finalized properties for audio encoder.
TheMutta 0be58ae
Added libsamplerate
TheMutta 41a8e06
Audio example.
TheMutta 85afbef
Mixer now supports fully AudioFrames
TheMutta ae0ca2c
Multithreaded mixer.
TheMutta c8457cb
Removed debug messages.
TheMutta 3406ece
AudioFrame to StreamMessageParser
TheMutta f860250
Added examples.
97a640a
Transformed AudioNodes private fields into protected.
a16bd47
Removed eccess function from audio nodes.
307aced
Added helper constructors to AudioFrame.
51eda95
Added missing frames metadata from AudioFrame
ff86777
TODO: mixer only runs on host
ee0da3f
Running encoder not on host in examples.
fb6b3ee
Clangformat
9f7427c
Added test for encoder.
b4df862
Fixed bug in frame calculation in AudioMixer
789b7ae
Encoder and mixer test.
bd40c69
Added RPC getAlsaDevices
c8228b0
Fixed audio list example.
c2329d2
Audio nodes python bindings.
f130cb7
Finalised python bindings for Audio nodes.
5119284
Added AudioFormat enum to be compatible with SF_FORMAT
e616c8d
Added AudioFormat binding.
6589a73
Added missing running host function on bindings for AudioOut
70b2bea
Added python Audio examples.
b2169ef
Added GetAlsaPCMs function
8c14ad5
Added GetAlsaPCMs callback
a41f224
Added verbose error messages to AudioIn and AudioOut nodes.
b5b8aa3
Moved snd_pcm_t *captureHandle to the run() function in AudioIn and A…
d4119af
Added additional tests in configuration stage of AudioIn
ad3852d
Added additional tests AudioOut
ffaee85
Added more explicative error messages + added snd_pcm_wait()
d1816e1
Fixed typo
7a3e91b
Update src/utility/AudioHelpers.cpp
TheMutta 475c44c
Update src/utility/AudioHelpers.cpp
TheMutta 1ed2951
Removed unused outputs from replay node.
23ea050
TODO: possible sw_params?
992f75a
getAlsaPCMs now returns both playback and capture devices.
1a60efc
Fixed capitalization issue in getAlsaDevices.
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -556,6 +556,8 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ | |
.def("setTimesync", [](DeviceBase& d, bool e) { py::gil_scoped_release release; return d.setTimesync(e); }, py::arg("enable"), DOC(dai, DeviceBase, setTimesync, 2)) | ||
.def("getDeviceName", [](DeviceBase& d) { std::string name; { py::gil_scoped_release release; name = d.getDeviceName(); } return py::bytes(name).attr("decode")("utf-8", "replace"); }, DOC(dai, DeviceBase, getDeviceName)) | ||
.def("getProductName", [](DeviceBase& d) { std::string name; { py::gil_scoped_release release; name = d.getProductName(); } return py::bytes(name).attr("decode")("utf-8", "replace"); }, DOC(dai, DeviceBase, getProductName)) | ||
.def("getAlsaDevices", [](DeviceBase& d) { py::gil_scoped_release release; return d.getAlsaDevices(); }, DOC(dai, DeviceBase, getAlsaDevices)) | ||
.def("getAlsaPCMs", [](DeviceBase& d) { py::gil_scoped_release release; return d.getAlsaPCMs(); }, DOC(dai, DeviceBase, getAlsaPCMs)) | ||
Comment on lines
+559
to
+560
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO:
|
||
; | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
bindings/python/src/pipeline/datatype/AudioFrameBindings.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#include "DatatypeBindings.hpp" | ||
#include "pipeline/CommonBindings.hpp" | ||
#include <unordered_map> | ||
#include <memory> | ||
|
||
// depthai | ||
#include "depthai/pipeline/datatype/AudioFrame.hpp" | ||
|
||
//pybind | ||
#include <pybind11/chrono.h> | ||
#include <pybind11/numpy.h> | ||
|
||
// #include "spdlog/spdlog.h" | ||
|
||
void bind_audioframe(pybind11::module& m, void* pCallstack){ | ||
|
||
using namespace dai; | ||
|
||
// py::class_<RawBuffer, std::shared_ptr<RawBuffer>> rawBuffer(m, "RawBuffer", DOC(dai, RawBuffer)); | ||
py::class_<AudioFrame, Py<AudioFrame>, Buffer, std::shared_ptr<AudioFrame>> audioFrame(m, "AudioFrame", DOC(dai, AudioFrame)); | ||
py::enum_<AudioFrame::AudioFormat> audioFormat(audioFrame, "AudioFormat"); | ||
|
||
audioFormat | ||
.value("AUDIO_FORMAT_PCM_S8", AudioFrame::AudioFormat::AUDIO_FORMAT_PCM_S8) | ||
.value("AUDIO_FORMAT_PCM_16", AudioFrame::AudioFormat::AUDIO_FORMAT_PCM_16) | ||
.value("AUDIO_FORMAT_PCM_24", AudioFrame::AudioFormat::AUDIO_FORMAT_PCM_24) | ||
.value("AUDIO_FORMAT_PCM_32", AudioFrame::AudioFormat::AUDIO_FORMAT_PCM_32) | ||
; | ||
|
||
|
||
/////////////////////////////////////////////////////////////////////// | ||
/////////////////////////////////////////////////////////////////////// | ||
/////////////////////////////////////////////////////////////////////// | ||
// Call the rest of the type defines, then perform the actual bindings | ||
Callstack* callstack = (Callstack*) pCallstack; | ||
auto cb = callstack->top(); | ||
callstack->pop(); | ||
cb(m, pCallstack); | ||
// Actual bindings | ||
/////////////////////////////////////////////////////////////////////// | ||
/////////////////////////////////////////////////////////////////////// | ||
/////////////////////////////////////////////////////////////////////// | ||
|
||
// // Metadata / raw | ||
// rawBuffer | ||
// .def(py::init<>()) | ||
// // .def_property("data", [](py::object &obj){ | ||
// // dai::RawBuffer &a = obj.cast<dai::RawBuffer&>(); | ||
// // return py::array_t<uint8_t>(a.data.size(), a.data.data(), obj); | ||
// // }, [](py::object &obj, py::array_t<std::uint8_t, py::array::c_style> array){ | ||
// // dai::RawBuffer &a = obj.cast<dai::RawBuffer&>(); | ||
// // a.data = {array.data(), array.data() + array.size()}; | ||
// // }) | ||
// .def_property("ts", | ||
// [](const RawBuffer& o){ | ||
// double ts = o.ts.sec + o.ts.nsec / 1000000000.0; | ||
// return ts; | ||
// }, | ||
// [](RawBuffer& o, double ts){ | ||
// o.ts.sec = ts; | ||
// o.ts.nsec = (ts - o.ts.sec) * 1000000000.0; | ||
// } | ||
// ) | ||
// .def_property("tsDevice", | ||
// [](const RawBuffer& o){ | ||
// double ts = o.tsDevice.sec + o.tsDevice.nsec / 1000000000.0; | ||
// return ts; | ||
// }, | ||
// [](RawBuffer& o, double ts){ | ||
// o.tsDevice.sec = ts; | ||
// o.tsDevice.nsec = (ts - o.tsDevice.sec) * 1000000000.0; | ||
// } | ||
// ) | ||
// .def_readwrite("sequenceNum", &RawBuffer::sequenceNum) | ||
// ; | ||
|
||
// Message | ||
audioFrame | ||
.def(py::init<>(), DOC(dai, AudioFrame, AudioFrame)) | ||
.def(py::init<size_t>(), DOC(dai, AudioFrame, AudioFrame, 2)) | ||
|
||
// obj is "Python" object, which we used then to bind the numpy arrays lifespan to | ||
.def("getData", [](py::object &obj){ | ||
// creates numpy array (zero-copy) which holds correct information such as shape, ... | ||
dai::AudioFrame &a = obj.cast<dai::AudioFrame&>(); | ||
return py::array_t<uint8_t>(a.getData().size(), a.getData().data(), obj); | ||
}, DOC(dai, AudioFrame, getData)) | ||
.def("setData", py::overload_cast<const std::vector<std::uint8_t>&>(&AudioFrame::setData), DOC(dai, AudioFrame, setData)) | ||
.def("setData", [](AudioFrame& audioFrame, py::array_t<std::uint8_t, py::array::c_style | py::array::forcecast> array){ | ||
audioFrame.setData({array.data(), array.data() + array.nbytes()}); | ||
}, DOC(dai, AudioFrame, setData)) | ||
.def("setData", [](AudioFrame& audioFrame, py::buffer data){ | ||
std::string str = data.cast<std::string>(); | ||
audioFrame.setData({str.data(), str.data() + str.size()}); | ||
}, DOC(dai, AudioFrame, setData)) | ||
.def("getTimestamp", &AudioFrame::getTimestamp, DOC(dai, AudioFrame, getTimestamp)) | ||
.def("getFrames", &AudioFrame::getFrames, DOC(dai, AudioFrame, getFrames)) | ||
.def("getBitrate", &AudioFrame::getBitrate, DOC(dai, AudioFrame, getBitrate)) | ||
.def("getChannels", &AudioFrame::getChannels, DOC(dai, AudioFrame, getChannels)) | ||
.def("getFormat", &AudioFrame::getFormat, DOC(dai, AudioFrame, getFormat)) | ||
.def("setTimestamp", &AudioFrame::setTimestamp, DOC(dai, AudioFrame, setTimestamp)) | ||
.def("setFrames", &AudioFrame::setFrames, DOC(dai, AudioFrame, setFrames)) | ||
.def("setBitrate", &AudioFrame::setBitrate, DOC(dai, AudioFrame, setBitrate)) | ||
.def("setChannels", &AudioFrame::setChannels, DOC(dai, AudioFrame, setChannels)) | ||
.def("setFormat", &AudioFrame::setFormat, DOC(dai, AudioFrame, setFormat)) | ||
; | ||
|
||
|
||
} |
34 changes: 34 additions & 0 deletions
34
bindings/python/src/pipeline/node/AudioEncoderBindings.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#include "Common.hpp" | ||
#include "NodeBindings.hpp" | ||
#include "depthai/pipeline/Node.hpp" | ||
#include "depthai/pipeline/Pipeline.hpp" | ||
#include "depthai/pipeline/node/AudioEncoder.hpp" | ||
|
||
void bind_audioencoder(pybind11::module& m, void* pCallstack) { | ||
using namespace dai; | ||
using namespace dai::node; | ||
using namespace pybind11::literals; | ||
|
||
// Declare node upfront | ||
auto audioEncoder = ADD_NODE(AudioEncoder); | ||
|
||
// Call the rest of the type defines, then perform the actual bindings | ||
Callstack* callstack = (Callstack*)pCallstack; | ||
auto cb = callstack->top(); | ||
callstack->pop(); | ||
cb(m, pCallstack); | ||
|
||
// Actual bindings | ||
audioEncoder.def_readonly("input", &AudioEncoder::input, DOC(dai, node, AudioEncoder, input)) | ||
.def_readonly("out", &AudioEncoder::out, DOC(dai, node, AudioEncoder, out)) | ||
.def("build", &AudioEncoder::build, DOC(dai, node, AudioEncoder, build)) | ||
.def("setBitrate", &AudioEncoder::setBitrate, DOC(dai, node, AudioEncoder, setBitrate)) | ||
.def("setChannels", &AudioEncoder::setChannels, DOC(dai, node, AudioEncoder, setChannels)) | ||
.def("setFormat", &AudioEncoder::setFormat, DOC(dai, node, AudioEncoder, setFormat)) | ||
.def("getBitrate", &AudioEncoder::getBitrate, DOC(dai, node, AudioEncoder, getBitrate)) | ||
.def("getChannels", &AudioEncoder::getChannels, DOC(dai, node, AudioEncoder, getChannels)) | ||
.def("getFormat", &AudioEncoder::getFormat, DOC(dai, node, AudioEncoder, getFormat)) | ||
.def("setRunOnHost", &AudioEncoder::setRunOnHost, py::arg("runOnHost"), DOC(dai, node, AudioEncoder, setRunOnHost)) | ||
.def("runOnHost", &AudioEncoder::runOnHost, DOC(dai, node, AudioEncoder, runOnHost)) | ||
; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#include "Common.hpp" | ||
#include "NodeBindings.hpp" | ||
#include "depthai/pipeline/Node.hpp" | ||
#include "depthai/pipeline/Pipeline.hpp" | ||
#include "depthai/pipeline/node/AudioIn.hpp" | ||
|
||
void bind_audioin(pybind11::module& m, void* pCallstack) { | ||
using namespace dai; | ||
using namespace dai::node; | ||
using namespace pybind11::literals; | ||
|
||
// Declare node upfront | ||
auto audioIn = ADD_NODE(AudioIn); | ||
|
||
// Call the rest of the type defines, then perform the actual bindings | ||
Callstack* callstack = (Callstack*)pCallstack; | ||
auto cb = callstack->top(); | ||
callstack->pop(); | ||
cb(m, pCallstack); | ||
|
||
// Actual bindings | ||
audioIn.def_readonly("out", &AudioIn::out, DOC(dai, node, AudioIn, out)) | ||
.def("build", &AudioIn::build, DOC(dai, node, AudioIn, build)) | ||
.def("setDeviceName", &AudioIn::setDeviceName, DOC(dai, node, AudioIn, setDeviceName)) | ||
.def("setDevicePath", &AudioIn::setDevicePath, DOC(dai, node, AudioIn, setDevicePath)) | ||
.def("setBitrate", &AudioIn::setBitrate, DOC(dai, node, AudioIn, setBitrate)) | ||
.def("setFps", &AudioIn::setFps, DOC(dai, node, AudioIn, setFps)) | ||
.def("setChannels", &AudioIn::setChannels, DOC(dai, node, AudioIn, setChannels)) | ||
.def("setFormat", &AudioIn::setFormat, DOC(dai, node, AudioIn, setFormat)) | ||
.def("getDeviceName", &AudioIn::getDeviceName, DOC(dai, node, AudioIn, getDeviceName)) | ||
.def("getDevicePath", &AudioIn::getDevicePath, DOC(dai, node, AudioIn, getDevicePath)) | ||
.def("getBitrate", &AudioIn::getBitrate, DOC(dai, node, AudioIn, getBitrate)) | ||
.def("getFps", &AudioIn::getFps, DOC(dai, node, AudioIn, getFps)) | ||
.def("getChannels", &AudioIn::getChannels, DOC(dai, node, AudioIn, getChannels)) | ||
.def("getFormat", &AudioIn::getFormat, DOC(dai, node, AudioIn, getFormat)) | ||
.def("setRunOnHost", &AudioIn::setRunOnHost, py::arg("runOnHost"), DOC(dai, node, AudioIn, setRunOnHost)) | ||
.def("runOnHost", &AudioIn::runOnHost, DOC(dai, node, AudioIn, runOnHost)) | ||
|
||
; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#include "Common.hpp" | ||
#include "NodeBindings.hpp" | ||
#include "depthai/pipeline/Node.hpp" | ||
#include "depthai/pipeline/Pipeline.hpp" | ||
#include "depthai/pipeline/node/AudioMixer.hpp" | ||
|
||
void bind_audiomixer(pybind11::module& m, void* pCallstack) { | ||
using namespace dai; | ||
using namespace dai::node; | ||
using namespace pybind11::literals; | ||
|
||
// Declare node upfront | ||
auto audioMixer = ADD_NODE(AudioMixer); | ||
|
||
// Call the rest of the type defines, then perform the actual bindings | ||
Callstack* callstack = (Callstack*)pCallstack; | ||
auto cb = callstack->top(); | ||
callstack->pop(); | ||
cb(m, pCallstack); | ||
|
||
// Actual bindings | ||
audioMixer.def_readonly("outputs", &AudioMixer::outputs, DOC(dai, node, AudioMixer, outputs)) | ||
.def_readonly("inputs", &AudioMixer::inputs, DOC(dai, node, AudioMixer, inputs)) | ||
.def("build", &AudioMixer::build,DOC(dai, node, AudioMixer, build)) | ||
.def("registerSource", &AudioMixer::registerSource, DOC(dai, node, AudioMixer, registerSource)) | ||
.def("registerSink", &AudioMixer::registerSink, DOC(dai, node, AudioMixer, registerSink)) | ||
.def("linkSourceToSink", &AudioMixer::linkSourceToSink, DOC(dai, node, AudioMixer, linkSourceToSink)) | ||
.def("unregisterSource", &AudioMixer::unregisterSource, DOC(dai, node, AudioMixer, unregisterSource)) | ||
.def("unregisterSink", &AudioMixer::unregisterSink, DOC(dai, node, AudioMixer, unregisterSink)) | ||
.def("unlinkSourceFromSink", &AudioMixer::unlinkSourceFromSink, DOC(dai, node, AudioMixer, unlinkSourceFromSink)) | ||
.def("setRunOnHost", &AudioMixer::setRunOnHost, py::arg("runOnHost"), DOC(dai, node, AudioMixer, setRunOnHost)) | ||
.def("runOnHost", &AudioMixer::runOnHost, DOC(dai, node, AudioMixer, runOnHost)) | ||
|
||
; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#include "Common.hpp" | ||
#include "NodeBindings.hpp" | ||
#include "depthai/pipeline/Node.hpp" | ||
#include "depthai/pipeline/Pipeline.hpp" | ||
#include "depthai/pipeline/node/AudioOut.hpp" | ||
|
||
void bind_audioout(pybind11::module& m, void* pCallstack) { | ||
using namespace dai; | ||
using namespace dai::node; | ||
using namespace pybind11::literals; | ||
|
||
// Declare node upfront | ||
auto audioOut = ADD_NODE(AudioOut); | ||
|
||
// Call the rest of the type defines, then perform the actual bindings | ||
Callstack* callstack = (Callstack*)pCallstack; | ||
auto cb = callstack->top(); | ||
callstack->pop(); | ||
cb(m, pCallstack); | ||
|
||
// Actual bindings | ||
audioOut.def_readonly("input", &AudioOut::input, DOC(dai, node, AudioOut, input)) | ||
.def("build", &AudioOut::build, DOC(dai, node, AudioOut, build)) | ||
.def("setDeviceName", &AudioOut::setDeviceName, DOC(dai, node, AudioOut, setDeviceName)) | ||
.def("setDevicePath", &AudioOut::setDevicePath, DOC(dai, node, AudioOut, setDevicePath)) | ||
.def("setBitrate", &AudioOut::setBitrate, DOC(dai, node, AudioOut, setBitrate)) | ||
.def("setFps", &AudioOut::setFps, DOC(dai, node, AudioOut, setFps)) | ||
.def("setChannels", &AudioOut::setChannels, DOC(dai, node, AudioOut, setChannels)) | ||
.def("setFormat", &AudioOut::setFormat, DOC(dai, node, AudioOut, setFormat)) | ||
.def("getDeviceName", &AudioOut::getDeviceName, DOC(dai, node, AudioOut, getDeviceName)) | ||
.def("getDevicePath", &AudioOut::getDevicePath, DOC(dai, node, AudioOut, getDevicePath)) | ||
.def("getBitrate", &AudioOut::getBitrate, DOC(dai, node, AudioOut, getBitrate)) | ||
.def("getFps", &AudioOut::getFps, DOC(dai, node, AudioOut, getFps)) | ||
.def("getChannels", &AudioOut::getChannels, DOC(dai, node, AudioOut, getChannels)) | ||
.def("getFormat", &AudioOut::getFormat, DOC(dai, node, AudioOut, getFormat)) | ||
.def("setRunOnHost", &AudioOut::setRunOnHost, py::arg("runOnHost"), DOC(dai, node, AudioOut, setRunOnHost)) | ||
.def("runOnHost", &AudioOut::runOnHost, DOC(dai, node, AudioOut, runOnHost)) | ||
; | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix indentation