Trouble playing mp3s after id3 image edit

2019-05-28 22:28发布

问题:

Due to hardware restrictions, the software we produce tries to ensure that any audio file it imports into it's library (ready to be copied onto the hardware) is an acceptable bit rate.

Recently we've started using FFmpeg to convert a number of different audio types to mp3 to allow them to be imported and used on our hardware. Whilst the conversion works fine and the mp3 files work on our hardware afterwards, we're having issues specifically when adding an album art to the ID3 tags of the mp3. The track will not play audio in our software. It also seems that Windows cannot pick up the values of the ID3 tags in explorer, but Windows Media Player will still play the track.

This problem only seems to occur when changing a newly converted mp3s' ID3 tags after using FFmpeg. Changing tags on mp3s from other sources or those that have already got ID3 tag album art is fine.

The code for using FFmpeg from our software is as follows:

        private const string SAMPLE_RATE = "44100";

        ...

        //create temp file for output
        outFile = Path.GetTempFileName();
        outFile = Path.ChangeExtension(outFile, "mp3");

        if (!File.Exists(inFile))
            return false;

        string metadata = (inFile.EndsWith("mp3")) ? " " : " -map_meta_data 0:0 ";

        //build process
        string workingDirectory = Environment.CurrentDirectory;
        ProcessStartInfo FFmpegProcessInfo = new ProcessStartInfo();
        FFmpegProcessInfo.WorkingDirectory = workingDirectory;
        FFmpegProcessInfo.FileName = "ffmpeg.exe";
        FFmpegProcessInfo.Arguments = "-i \"" + inFile + "\"" +  " -ar "+SAMPLE_RATE + metadata + "\"" + outFile + "\""; //default conversion to SAMPLE_RATE
        FFmpegProcessInfo.CreateNoWindow = true; //hide from user
        //let us grab the output
        FFmpegProcessInfo.RedirectStandardError = true;
        FFmpegProcessInfo.RedirectStandardOutput = true;
        FFmpegProcessInfo.UseShellExecute = false;
        Process p = Process.Start(FFmpegProcessInfo);

To change the ID3 tags we have started using TagLib-Sharp and the code for changing the ID3 tags is:

    public void SetId3Tags(string path, Bitmap image, IDictionary<string, string> values)
    {
        FileInfo fileInfo = new FileInfo(path);
        fileInfo.Attributes = FileAttributes.Normal;

        try
        {
            TagLib.File tagFile = TagLib.File.Create(path);
            if (values.ContainsKey("Title"))
                tagFile.Tag.Title = values["Title"];
            if (values.ContainsKey("Artist"))
                tagFile.Tag.Performers = new string[1] { values["Artist"] };
            if (values.ContainsKey("Comments"))
                tagFile.Tag.Comment = values["Comments"];
            if (image != null) {
                string tmpImg = Path.GetTempFileName();
                image.Save(tmpImg);
                IPicture newArt = new Picture(tmpImg);
                tagFile.Tag.Pictures = new IPicture[1] {newArt};
            }

            tagFile.Save();

        }
        catch (Exception e) 
        { 
            _logger.Log(e); 
        }
    }

And the code used to play the track in the software (FilgraphManager in QuartzTypeLib):

public void Play()
    {
        if (!_isPaused)
        {
            _graphManager = new FilgraphManager();
            _mp3control = (IMediaControl)_graphManager;
            _mp3position = (IMediaPosition)_graphManager;
            _tempFile = Path.GetTempFileName();
            File.Copy(_fullPath, _tempFile, true);  

            _mp3control.RenderFile(_tempFile);
        }
        else
        {
            _isPaused = false;
        }
        _mp3control.Run();
    }

And the error when executing _mp3control.RenderFile(_tempFile):

{System.Runtime.InteropServices.ExternalException} = {"Exception from HRESULT: 0x80040266"}
at QuartzTypeLib.FilgraphManagerClass.RenderFile(String strFilename)

My largest problem here is that I don't know whether the fault lies with (our implementation of) FFmpeg (large library that's used fine in many other places), TagLib-Sharp or the audio playing.

Edit 1: Following J. Andrew Laughlin's advice I've been looking at the differences of the ID3 tags in the hex of each file. This is what I've found:

The initial input is ID3v2.3. After re-encoding with FFmpeg, the ID3 data is v2.4. This initial re-encoded file plays fine in media players and our software. Using TagLib# in our software to add album art keeps ID3v2.4 but the tags are only available using TagLib# to read them and it only plays in media players such as Windows Media Player. Using another tool to change the ID3 tags (in this case AudioShell Tag Editor) and add the same album art changed the ID3 version to 2.3 and meant that the mp3 played on our softwares audio player as well as other media players - However changing the tags afterwards produces an exception when saving the image.

One other thing I tried was to rip out the ID3v2.4 block completely after the re-encoding, this plays (as you'd expect) in all media players. When using the TagLib# on this untagged file, the tags were correctly applied (v2.3) and it continued to play properly in our software as well as others.

Unless anyone can suggest an elegant solution (either force TagLib# to write a new ID3v2.3 block or stop FFmpeg from writing one at all) I think I may just programmatically remove the ID3v2.4 block from the file after encoding and then write a new one.

回答1:

TagLib# can be used to "downgrade" an ID3 tag from 2.4 to 2.3. I personally prefer to convert my ID3 tags to 2.3 since it is more consistently adopted across music players.

It's been a while, but I believe you can use the following in your above code:

TagLib.Id3v2.Tag id3v2tag = tagFile.GetTag(TagLib.TagTypes.Id3v2, false);

if(id3v2tag != null)
    id3v2tag.Version = 3;

tagFile.Save();

Alternatively, you can force all tags to render in 2.3 by using the following code when your application initializes:

TagLib.Id3v2.Tag.DefaultVersion = 3;
TagLib.Id3v2.Tag.ForceDefaultVersion = true;

TagLib# can also remove tags completely and re-add them, but it shouldn't have to come to that.

Good luck!