Skip to content
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

Lyra support #3949

Merged
merged 7 commits into from
Jun 13, 2024
Merged

Lyra support #3949

merged 7 commits into from
Jun 13, 2024

Conversation

trengginas
Copy link
Member

@trengginas trengginas commented May 10, 2024

This PR will add support for lyra codec (https://github.com/google/lyra).

Requirement:

  • Lyra codec library (version 1.3), download from here

Notes and limitations:

  • The implementation has only been tested using 1.3. Other versions might not work and have compatibility issues.
  • Currently there's no standards related to SDP specification for Lyra. The rtpmap SDP attribute will follow the specification defined in rfc8866. It will also specify the bitrate setting as an fmtp param. e.g.:
    a=rtpmap:96 lyra/16000
    a=fmtp:96 bitrate=3200
    
  • Currently Lyra only supports Android, Linux, Mac and Windows (no iOS). On windows, the Lyra build only tested for x64 and Release mode only.

Building Lyra:

  • Check the requirements for building the lyra codec on their github pages.
  • Specific for v1.3, the working (tested) bazel version is 5.0.0 (here)

Build commands for Win, Mac, Linux

bazel build -c opt  :encoder_main
bazel build -c opt  :decoder_main

Build commands for Android

bazel build -c opt android_example:lyra_android_example --config=android_arm64 --copt=-DBENCHMARK

Setup include and lib folder

Bazel will download and build the required third party code used by Lyra. We can use this file libgen.zip to generate a single static library and header files required by pjsip.
Run the script from the Lyra source folder.

python3 libgen.py

For windows, add --platform=win to the command.

Add Lyra support

For GNU targets (Mac, Linux, Android):

  • Run configure or configure-android command and specify the Lyra source folder using --with-lyra option.
    ./configure --with-lyra=[lyra_src_folder]

    You should see this on the output if everything is in order.
    checking lyra usability... yes

  • continue building the library

For Windows/Visual Studio (only works for Release/x64 build config)

  • Add #define PJMEDIA_HAS_LYRA_CODEC 1 in config_site.h file
  • Add the include folders to the project settings:
- [lyra_src_folder]
- [lyra_src_folder]\include\com_google_absl
- [lyra_src_folder]\include\com_google_glog
- [lyra_src_folder]\include\gulrak_filesystem
  • Add the lib folder to the project settings:
- [lyra_src_folder]\lib

Configuring the codec:

  • Lyra supports 3200, 6000, 9200 bitrate. You can specify the bitrate from pjmedia_codec_lyra_config::bit_rate/CodecLyraConfig::bit_rate.
    The value represents the decoder bitrate requested by the receiver. Endpoints can be configured with different bitrates. For example, the local endpoint might be set to a bitrate of 3200, while the remote endpoint is set to 6000. In this scenario, the remote endpoint will send data at 3200 bitrate, while the local endpoint will send data at 6000 bitrate
  • Lyra required some additional (model) files (lyra_config.binarypb, lyragan.tflite, quantizer.tflite, soundstream_encoder.tflite).
    Fortunately, Lyra provided those files on their source folder (model-coeffs). You can copy those files to a new folder and specify it to pjmedia_codec_lyra_config::model_path/CodecLyraConfig::model_path.
  • [For android app]. You can include the model files above as assets to your project and later copy them to the local folder and specify it as CodecLyraConfig::model_path. Check here as reference.

@trengginas trengginas added this to the release-2.15 milestone May 10, 2024
@trengginas trengginas self-assigned this May 10, 2024
@sauwming
Copy link
Member

sauwming commented May 10, 2024

The version selection is very tricky indeed, can only work for specific Lyra and Bazel version. I tried Lyra 1.3.2 with Bazel 5.3.2 and it didn't work, while 1.3.0 and 5.0.0 was a success.

The option --with-lyra is not properly declared, btw.
aconfigure:11875: WARNING: unrecognized options: --with-lyra

The detection also failed:

aconfigure:10151: checking lyra usability
aconfigure:10185: g++ -o conftest -std=c++17 -I/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl -I/Users/ming/teluu/tools/lyra-1.3.0/include/gulrak_filesystem -I/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_glog/src -I/Users/ming/teluu/tools/lyra-1.3.0 -g -O2 -DGLOG_DEPRECATED=__attribute__((deprecated)) -DGLOG_EXPORT=__attribute__((visibility("default"))) -DGLOG_NO_EXPORT=__attribute__((visibility("default")))  -L/Users/ming/teluu/tools/lyra-1.3.0/lib    -lupnp -lixml conftest.cpp -llyra -lopus -lssl -lcrypto -lupnp -lixml -lm -lpthread  -framework CoreAudio -framework CoreServices -framework AudioUnit -framework AudioToolbox -framework Foundation -framework AppKit -framework AVFoundation -framework CoreGraphics -framework QuartzCore -framework CoreVideo -framework CoreMedia -framework Metal -framework MetalKit -framework VideoToolbox -L/opt/local/lib -lSDL2  -L/usr/local/lib -lavdevice -lavformat -lavcodec -lswscale -lavutil >&5
In file included from conftest.cpp:84:
/Users/ming/teluu/tools/lyra-1.3.0/lyra_decoder.h:25:10: fatal error: 'absl/types/span.h' file not found
#include "absl/types/span.h"
         ^~~~~~~~~~~~~~~~~~~

@nanangizz
Copy link
Member

How about our SDP specs (e.g: codec name, supported parameters (mandatory & optional)), perhaps it is a good idea to describe it here too?

@trengginas
Copy link
Member Author

The version selection is very tricky indeed, can only work for specific Lyra and Bazel version. I tried Lyra 1.3.2 with Bazel 5.3.2 and it didn't work, while 1.3.0 and 5.0.0 was a success.

The option --with-lyra is not properly declared, btw. aconfigure:11875: WARNING: unrecognized options: --with-lyra

The detection also failed:

aconfigure:10151: checking lyra usability
aconfigure:10185: g++ -o conftest -std=c++17 -I/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl -I/Users/ming/teluu/tools/lyra-1.3.0/include/gulrak_filesystem -I/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_glog/src -I/Users/ming/teluu/tools/lyra-1.3.0 -g -O2 -DGLOG_DEPRECATED=__attribute__((deprecated)) -DGLOG_EXPORT=__attribute__((visibility("default"))) -DGLOG_NO_EXPORT=__attribute__((visibility("default")))  -L/Users/ming/teluu/tools/lyra-1.3.0/lib    -lupnp -lixml conftest.cpp -llyra -lopus -lssl -lcrypto -lupnp -lixml -lm -lpthread  -framework CoreAudio -framework CoreServices -framework AudioUnit -framework AudioToolbox -framework Foundation -framework AppKit -framework AVFoundation -framework CoreGraphics -framework QuartzCore -framework CoreVideo -framework CoreMedia -framework Metal -framework MetalKit -framework VideoToolbox -L/opt/local/lib -lSDL2  -L/usr/local/lib -lavdevice -lavformat -lavcodec -lswscale -lavutil >&5
In file included from conftest.cpp:84:
/Users/ming/teluu/tools/lyra-1.3.0/lyra_decoder.h:25:10: fatal error: 'absl/types/span.h' file not found
#include "absl/types/span.h"
         ^~~~~~~~~~~~~~~~~~~

Just fixed the --with-lyra option declaration. For the error, have you execute the python script above? It is needed to setup the static library and copy the required header files to the include/lib folder.

@sauwming
Copy link
Member

Just fixed the --with-lyra option declaration. For the error, have you execute the python script above? It is needed to setup the static library and copy the required header files to the include/lib folder.

Ah, I thought the python script is under the Build commands for Android section.

Executing the script, the lib generation in lib/liblyra.a succeeded, but the header copying failed:
distutils.errors.DistutilsFileError: cannot copy tree '/private/var/tmp/_bazel_ming/54ee470338414c00ed158de552250b16/external/com_google_absl': not a directory

attr->info.pt = (pj_uint8_t)id->pt;
attr->info.channel_cnt = 1;

idx = attr->info.pt - PJMEDIA_RTP_PT_LYRA_8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that PT can be rewritten, using it in index calculation here without range check may or may not be harmful (some old codecs may still use it though). Perhaps better avoiding this if there is a more reliable way (e.g: comparing clock rate perhaps)?

pjmedia_frame frames[])
{
unsigned count = 0;
unsigned frm_size = lyra_cfg.bit_rate / (50 * 8);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the lyra_cfg.bit_rate always the same between local & remote?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently yes. We can add the information on the SDP, however it would be proprietary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean does Lyra mandate that both sides must use the same bitrate? If not, then assuming both using same bit rate does not sound very safe, even for communication among PJSIP endpoints, as the bitrate seems to be configurable.

If Lyra does not provide packetizing APIs (haven't checked myself), IMO a safer approach would be not allowing multiple frames per packet, this is simple but perhaps not a good idea for low bitrate codecs. Another approach may be the parse just returns the whole encoded Lyra payload in the first frame and zero length data for any next frames (haven't checked if this is possible).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that the decoder expects a single frame when decoding. It will be an issue if the payload has multiple frames.
I think using a SDP setting is the correct way, although currently, it would be proprietary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using proprietary SDP is fine to me (without standard/rfc, the codec name is also kind of proprietary?). So the decoder will tell remote via its SDP (as usually SDP is about publishing decoder capability) about its expected bitrate when multiple frames per packet is used by the encoder?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added the bitrate setting on the SDP.

a=rtpmap:96 lyra/16000
a=fmtp:96 bitrate=3200

The bitrate information is needed for parsing, regardless multiple frames used/not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to clarify in the docs:

  • whether the bitrate in the SDP is requested by receiver/decoder or an information from sender/encoder,
  • what will happen if SDP offer-answer use different bitrates.

auto decoded = lyra_data->dec->DecodeSamples(samples_to_request);
if (!decoded.has_value()) {
PJ_LOG(4, (THIS_FILE, "Decode failed!"));
return PJMEDIA_CODEC_EFAILED;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlock mutex?

@nanangizz
Copy link
Member

Btw, in case you manage to find (and test against) any other SIP phone/B2BUA/server supporting Lyra, I think it would be great to be mentioned here too.

@trengginas
Copy link
Member Author

Just fixed the --with-lyra option declaration. For the error, have you execute the python script above? It is needed to setup the static library and copy the required header files to the include/lib folder.

Ah, I thought the python script is under the Build commands for Android section.

Executing the script, the lib generation in lib/liblyra.a succeeded, but the header copying failed: distutils.errors.DistutilsFileError: cannot copy tree '/private/var/tmp/_bazel_ming/54ee470338414c00ed158de552250b16/external/com_google_absl': not a directory

The bazel build process will download all the required third party on its output_base settings. You can check manually if the folder exists. And if it doesn't exists you can try rebuild the lyra library.
I use this command to build the library:

bazel build -c opt :encoder_main
bazel build -c opt :decoder_main

To clean up the build output, you can use:

bazel clean --expunge

I tried rebuilding from a fresh folder of lyra source and the header files are there.
Tested here building on windows, mac, linux and android.
Not sure if bazel behaves differently on other machines.

@sauwming
Copy link
Member

Okay, cleaning and rebuilding Lyra solved the issue.

  • A few build warning:
../src/pjmedia-codec/lyra.cpp:252:33: warning: ISO C++11 does not allow conversion from string literal to 'char *' [-Wwritable-strings]
    const pj_str_t lyra_tag = { "lyra", 4 };
                                ^
../src/pjmedia-codec/lyra.cpp:335:47: warning: ISO C++11 does not allow conversion from string literal to 'char *' [-Wwritable-strings]
        codecs[*count].encoding_name = pj_str("lyra");
                                              ^
../src/pjmedia-codec/lyra.cpp:442:55: warning: field precision should have type 'int', but argument has type 'pj_ssize_t' (aka 'long') [-Werror,-Wformat]
    PJ_LOG(4, (THIS_FILE, "Codec opened, model_path=%.*s, chan_cnt=%d, "
  • For the model files, there seem to be two options for our sample apps:
    a. Perform the copying in our Makefile (i.e. copy from with_lyra_path/model_coeffs to pjsip_apps/bin/model_coeffs).
    b. Alternatively, set PJMEDIA_CODEC_LYRA_DEFAULT_PATH to with_lyra_path/model_coeffs (no copying needed).

Also, perhaps provide more detailed instruction on how to perform the copying and specifying the path for Android?

- Add bitrate config on SDP, allowing different bitrate on local and remote
- Fix some compile warning
- Set [--with-lyra]/model_coeffs as default to PJMEDIA_CODEC_LYRA_DEFAULT_MODEL_PATH
@@ -83,6 +83,10 @@ enum pjmedia_audio_pt
PJMEDIA_RTP_PT_G7221_RSV1, /**< G722.1 reserve */
PJMEDIA_RTP_PT_G7221_RSV2, /**< G722.1 reserve */
PJMEDIA_RTP_PT_OPUS, /**< OPUS */
PJMEDIA_RTP_PT_LYRA_8, /**< LYRA @ 8Kbps */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kbps or KHz???

*/
struct CodecLyraConfig
{
unsigned bit_rate; /**< The expected bit rate from remote. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a note, the naming convention in pjsua2 is bitRate, instead of bit_rate. But since Opus is already wrong, I guess we can leave it as is.

# pragma warning(disable: 4100) // Possible loss of data
#endif

#include "lyra_encoder.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we can suppress these warnings?

In file included from /Users/ming/teluu/tools/lyra-1.3.0/lyra_encoder.h:25:
In file included from /Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/types/span.h:67:
/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/meta/type_traits.h:301:36: warning: builtin __has_trivial_destructor is deprecated; use __is_trivially_destructible instead [-Wdeprecated-builtins]
    : std::integral_constant<bool, __has_trivial_destructor(T) &&
                                   ^
/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/meta/type_traits.h:350:36: warning: builtin __has_trivial_constructor is deprecated; use __is_trivially_constructible instead [-Wdeprecated-builtins]
    : std::integral_constant<bool, __has_trivial_constructor(T) &&
                                   ^
/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/meta/type_traits.h:494:17: warning: builtin __has_trivial_assign is deprecated; use __is_trivially_assignable instead [-Wdeprecated-builtins]
          bool, __has_trivial_assign(typename std::remove_reference<T>::type) &&
                ^
/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/meta/type_traits.h:559:8: warning: builtin __has_trivial_copy is deprecated; use __is_trivially_copyable instead [-Wdeprecated-builtins]
      (__has_trivial_copy(ExtentsRemoved) || !kIsCopyOrMoveConstructible) &&
       ^
/Users/ming/teluu/tools/lyra-1.3.0/include/com_google_absl/absl/meta/type_traits.h:560:8: warning: builtin __has_trivial_assign is deprecated; use __is_trivially_assignable instead [-Wdeprecated-builtins]
      (__has_trivial_assign(ExtentsRemoved) || !kIsCopyOrMoveAssignable) &&

//PJ_LOG(4, (THIS_FILE, "Codec recover"));

/* output_buf_len is unreferenced when building in Release mode */
PJ_UNUSED_ARG(output_buf_len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed?

- Update some doc
- Change field setting name
- Remove un-needed code
- Suppress compile warning
lyra_data->enc_bit_rate != 9200))
{
lyra_data->enc_bit_rate = PJMEDIA_CODEC_LYRA_DEFAULT_BIT_RATE;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps better to also get the decoder bitrate from SDP as the app is allowed to modify the SDP?

pkt_size -= frm_size;

++count;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it is better to be a bit more lenient when remote does not follow bitrate param in SDP, e.g: when count==1 and frm_size < pkt_size, set the frames[0].size = pkt_size?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition count==1 and frm_size < pkt_size is still within the loop.
shouldn't it be:

    pj_size_t orig_pkt_size = pkt_size;
    while (pkt_size >= frm_size && count < *frame_cnt) {
        frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
        frames[count].buf = pkt;
        frames[count].size = frm_size;
        frames[count].timestamp.u64 = ts->u64 +
                                          lyra_data->samples_per_frame * count;
        pkt = ((char*)pkt) + frm_size;
        pkt_size -= frm_size;

        ++count;
    }
    if (pkt_size != 0 && count == 1) {
        frames[0].size = orig_pkt_size;
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the idea is to avoid truncating the payload if it contains only one frame.

* endpoint will send data at 3200 bitrate, while the local endpoint
* will send data at 6000 bitrate.
*/
unsigned bit_rate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe the valid & default bitrates too?
And perhaps also clockrates?

lyra_factory.param[2].pt = PJMEDIA_RTP_PT_LYRA_32;
lyra_factory.param[2].clock_rate = 32000;

lyra_factory.param[3].enabled = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to enable the disabled clock_rates?

- Add comments
- Add compile time setting to enable certain samplerate
- Get decoder bitrate from SDP
- More lenient on codec parse
@trengginas trengginas merged commit 28c54a4 into master Jun 13, 2024
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants