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

Add initial support for Core Audio Format / CAF files #232

Merged
merged 21 commits into from Jan 16, 2024

Conversation

irh
Copy link
Contributor

@irh irh commented Sep 19, 2023

This PR introduces support for decoding .caf files.

The implementation is derived from the spec, and tested against a local collection of audio files that use various encoding formats.

I haven't added decoding support for all chunk types, but hopefully this PR acts as a good starting point for further development?

Things seem to be working correctly, but please let me know if I've misunderstood some aspect of the format reader API, I'll be happy to make changes as needed.

@dedobbin
Copy link
Contributor

dedobbin commented Jan 7, 2024

I tested it with files from https://filesamples.com/formats/caf and https://www.dwsamplefiles.com/download-caf-sample-files/.
They all play correctly in symphonia-play, audio seems perfect when compared to Audacity. Track length is also correctly displayed.
Also all pass symphonia-check with default settings.

Code also looks good to me.

Good stuff.

@pdeljanov
Copy link
Owner

Thanks submitting this, I can see a lot of work was put into it and we'd be happy to have it.

Generally, I think it's okay to pull in a decoder/demuxer with a minimum set of functionality. It looks like you have PCM encapsulation down, so this would be a good start.

Unfortunately, I have no knowledge or experience with CAF, so would you be willing to address any bugs that come up with it (we could assign them to you)? Also, would you like to continue extending the support to other encapsulated codecs?

@irh
Copy link
Contributor Author

irh commented Jan 9, 2024

Hi @dedobbin + @pdeljanov, thanks for taking a look, and I'm glad to hear that you'd be happy to accept the PR.

Also, would you like to continue extending the support to other encapsulated codecs?

Compressed codec support is implemented (see the last commit), but maybe I misunderstood something?

What's missing from this implementation is support for metadata chunks like markers or annotations, but I wasn't sure which features would be available through Symphonia, and I figured that support should be added with specific use cases in mind.

Unfortunately, I have no knowledge or experience with CAF, so would you be willing to address any bugs that come up with it (we could assign them to you)?

Sure, I'd be happy to take a look if/when bugs come up, and I can review PRs with feature additions if you like.

@irh
Copy link
Contributor Author

irh commented Jan 9, 2024

@dedobbin I rebased the branch onto the latest master because it's been a few months (things still seem to be working correctly!), and I also fixed some clippy warnings.

@pdeljanov
Copy link
Owner

pdeljanov commented Jan 9, 2024

Just a heads up, the format check is failing because our config requires the nightly toolchain to adjust the bracket style. If you install the nightly toolchain you can use it by adding +nightly after cargo, e.g., cargo +nightly fmt.

Also, would you like to continue extending the support to other encapsulated codecs?

Compressed codec support is implemented (see the last commit), but maybe I misunderstood something?

What's missing from this implementation is support for metadata chunks like markers or annotations, but I wasn't sure which features would be available through Symphonia, and I figured that support should be added with specific use cases in mind.

Got it! I probably just misunderstood what you mean't!

@irh
Copy link
Contributor Author

irh commented Jan 10, 2024

@pdeljanov I've run cargo +nightly fmt and pushed.

Copy link
Owner

@pdeljanov pdeljanov left a comment

Choose a reason for hiding this comment

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

I've completed a more thorough review of this PR. While going over this I used Apple's specification as a reference.

Overall, I think this is very nicely done! Hopefully we can get this in very soon. 😄

Most of my concerns are related to data validation. The media stream should be considered untrustworthy and even malicious. Therefore, it's important we don't blindly trust what is being read. Less sinister scenarios could include damaged files. Ultimately, we don't ever want to panic or get thrown into an infinite loop due to bad input. Consider checking out the fuzzing setup used for MP3 if you really want to stress your code. That being said, I'm not going to gate this review on fuzz tests.

symphonia-format-caf/src/chunks.rs Show resolved Hide resolved
symphonia-format-caf/src/chunks.rs Outdated Show resolved Hide resolved
symphonia-format-caf/src/chunks.rs Show resolved Hide resolved
format_id,
bytes_per_packet: reader.read_be_u32()?,
frames_per_packet: reader.read_be_u32()?,
channels_per_frame: reader.read_be_u32()?,
Copy link
Owner

Choose a reason for hiding this comment

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

It is illegal for channels_per_frame to be 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a check for the channel count being zero in CafReader::read_audio_description_chunk(), but I think it makes sense to report an error as soon as possible. Does it make sense to leave the extra check in place (I guess the alternative is replacing it with unreachable!())?

Copy link
Owner

Choose a reason for hiding this comment

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

Either way works for me.

In such cases, I think I'd only strongly prefer the latter over the former if the additional check was in a hot code path. Mainly because we already setup the precondition that the field will never be 0 so why waste the cycles? However, I don't think FormatReaders are ever really "hot" so you can choose whatever you think is best for maintainability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think for maintainability replacing the error with an assertion makes sense, otherwise the maintainer is left wondering if there's ever a case where the first check could pass and then the second check could fail. There's no such case, so an assertion communicates that more clearly.

symphonia-format-caf/src/chunks.rs Outdated Show resolved Hide resolved
symphonia-format-caf/src/chunks.rs Show resolved Hide resolved
symphonia-format-caf/src/chunks.rs Outdated Show resolved Hide resolved
symphonia-format-caf/src/demuxer.rs Show resolved Hide resolved
}
else {
error!("Invalid packet index: {}", current_packet_index);
return decode_error("Invalid packet index");
Copy link
Owner

Choose a reason for hiding this comment

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

We have some conventions regarding the messages used for error message and logging. Use only use lowercase for both types.

For errors, since the source module is not known, please prefix the format or decoder name. In this case, please use the prefix caf: . You don't need to do this with the logging macros because they record the module emitting the log.

Please adjust accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, although I couldn't build with 1.53 to check that I haven't used any other post-1.53 features (there's a problem with finding a supported version of regex).

Copy link
Owner

Choose a reason for hiding this comment

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

Ah, I see. The regex crate is being pulled in by env_logger which is used by symphonia-play and symphonia-check, but not the main libraries. I tried building with 1.53.0 though, and it seems the log crate now requires 1.60.0, so this is unfortunate. We can ignore this then.

symphonia-format-caf/src/demuxer.rs Outdated Show resolved Hide resolved
@irh
Copy link
Contributor Author

irh commented Jan 12, 2024

Thanks for the review @pdeljanov, I've followed up on your feedback and pushed the changes, although I haven't double checked against my test files yet. I should have time to check them out over the weekend, I'll follow up when I'm done.

let chunk_size_u64 = chunk_size as u64;
let edit_count_offset = size_of::<u32>() as u64;

if chunk_size != -1 && chunk_size_u64 < edit_count_offset {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggestion: I feel this check is a bit too clever and harms readability a bit. Atleast until one recalls signed two's complement. Consider doing it the more "naive" way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I've tried to simplify this a bit.

format_id,
bytes_per_packet: reader.read_be_u32()?,
frames_per_packet: reader.read_be_u32()?,
channels_per_frame: reader.read_be_u32()?,
Copy link
Owner

Choose a reason for hiding this comment

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

Either way works for me.

In such cases, I think I'd only strongly prefer the latter over the former if the additional check was in a hot code path. Mainly because we already setup the precondition that the field will never be 0 so why waste the cycles? However, I don't think FormatReaders are ever really "hot" so you can choose whatever you think is best for maintainability.

symphonia-format-caf/src/chunks.rs Outdated Show resolved Hide resolved
@pdeljanov
Copy link
Owner

Hey @irh,

Thanks, looks good to me! We can merge this when you're ready.

Before then, you should also add yourself to the CONTRIBUTORS file. Also, if you can, please update the feature tables in /README.md, /symphonia/README.md, and /symphonia/src/lib.rs. Since this is the initial release, symphonai-format-caf should have a Good rating.

@irh
Copy link
Contributor Author

irh commented Jan 15, 2024

OK thanks for taking the time to review this and for the great feedback, I think we're good to go 👍

@pdeljanov
Copy link
Owner

Sounds good @irh! Do you mind re-basing this to resolve the conflicts?

@irh
Copy link
Contributor Author

irh commented Jan 16, 2024

Do you mind re-basing this to resolve the conflicts?

Done 👍

@pdeljanov pdeljanov merged commit abb497b into pdeljanov:master Jan 16, 2024
8 checks passed
@pdeljanov
Copy link
Owner

Merged! Thank you for the work you put into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants