diff options
author | Linus Nielsen Feltzing <linus@haxx.se> | 2005-07-05 08:43:36 +0000 |
---|---|---|
committer | Linus Nielsen Feltzing <linus@haxx.se> | 2005-07-05 08:43:36 +0000 |
commit | eaf8b2d76d3f69b919ff1c3a55e7ad2f07456bd4 (patch) | |
tree | 74c9b4d7e08b2378b499d63f13c8ea70f74f8351 /apps/metadata.c | |
parent | a10bb59331b5e48adf307c4538333e9a4e036db9 (diff) | |
download | rockbox-eaf8b2d76d3f69b919ff1c3a55e7ad2f07456bd4.tar.gz rockbox-eaf8b2d76d3f69b919ff1c3a55e7ad2f07456bd4.zip |
Patch #1232549 by Ryan Jackson, adds seeking and comments to Vorbis playback
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7025 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/metadata.c')
-rw-r--r-- | apps/metadata.c | 223 |
1 files changed, 221 insertions, 2 deletions
diff --git a/apps/metadata.c b/apps/metadata.c index f59917ceb8..6ba2331d26 100644 --- a/apps/metadata.c +++ b/apps/metadata.c | |||
@@ -91,9 +91,14 @@ const long wavpack_sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, | |||
91 | 91 | ||
92 | static bool get_apetag_info (struct mp3entry *entry, int fd); | 92 | static bool get_apetag_info (struct mp3entry *entry, int fd); |
93 | 93 | ||
94 | static bool get_vorbis_comments (struct mp3entry *entry, int fd); | ||
95 | |||
96 | static void little_endian_to_native (void *data, char *format); | ||
97 | |||
94 | bool get_metadata(struct track_info* track, int fd, const char* trackname, | 98 | bool get_metadata(struct track_info* track, int fd, const char* trackname, |
95 | bool v1first) { | 99 | bool v1first) { |
96 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; | 100 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; |
101 | unsigned long serialno=0, last_serialno=0; | ||
97 | int bytesperframe; | 102 | int bytesperframe; |
98 | unsigned char* buf; | 103 | unsigned char* buf; |
99 | int i,j,eof; | 104 | int i,j,eof; |
@@ -259,13 +264,29 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, | |||
259 | return(false); | 264 | return(false); |
260 | } | 265 | } |
261 | 266 | ||
267 | /* We need to ensure the serial number from this page is the | ||
268 | * same as the one from the last page (since we only support | ||
269 | * a single bitstream). | ||
270 | */ | ||
271 | serialno = buf[14]|(buf[15]<<8)|(buf[16]<<16)|(buf[17]<<24); | ||
272 | |||
262 | /* Ogg stores integers in little-endian format. */ | 273 | /* Ogg stores integers in little-endian format. */ |
263 | track->id3.filesize=filesize(fd); | 274 | track->id3.filesize=filesize(fd); |
264 | track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); | 275 | track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); |
265 | channels=buf[39]; | 276 | channels=buf[39]; |
266 | 277 | ||
278 | if ( !get_vorbis_comments(&(track->id3), fd) ) { | ||
279 | logf("get_vorbis_comments failed"); | ||
280 | return(false); | ||
281 | } | ||
282 | |||
283 | /* Set id3 genre to something bogus, otherwise vorbis tracks | ||
284 | * without genre tags will show up as 'Blues' | ||
285 | */ | ||
286 | track->id3.genre=255; | ||
287 | |||
267 | /* We now need to search for the last page in the file - identified by | 288 | /* We now need to search for the last page in the file - identified by |
268 | by ('O','g','g','S',0) and retrieve totalsamples */ | 289 | by ('O','g','g','S',0) and retrieve totalsamples */ |
269 | 290 | ||
270 | lseek(fd, -32*1024, SEEK_END); | 291 | lseek(fd, -32*1024, SEEK_END); |
271 | eof=0; | 292 | eof=0; |
@@ -283,8 +304,9 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, | |||
283 | i=0; | 304 | i=0; |
284 | while (i < (j-5)) { | 305 | while (i < (j-5)) { |
285 | if (memcmp(&buf[i],"OggS",5)==0) { | 306 | if (memcmp(&buf[i],"OggS",5)==0) { |
286 | if (i < (j-10)) { | 307 | if (i < (j-17)) { |
287 | totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); | 308 | totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); |
309 | last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24); | ||
288 | j=0; /* We can discard the rest of the buffer */ | 310 | j=0; /* We can discard the rest of the buffer */ |
289 | } else { | 311 | } else { |
290 | break; | 312 | break; |
@@ -300,7 +322,18 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, | |||
300 | j=0; | 322 | j=0; |
301 | } | 323 | } |
302 | } | 324 | } |
325 | |||
326 | /* This file has mutiple vorbis bitstreams (or is corrupt) */ | ||
327 | /* FIXME we should display an error here */ | ||
328 | if (serialno != last_serialno) { | ||
329 | track->taginfo_ready=false; | ||
330 | logf("serialno mismatch"); | ||
331 | logf("%ld", serialno); | ||
332 | logf("%ld", last_serialno); | ||
333 | return false; | ||
334 | } | ||
303 | 335 | ||
336 | track->id3.samples=totalsamples; | ||
304 | track->id3.length=(totalsamples/track->id3.frequency)*1000; | 337 | track->id3.length=(totalsamples/track->id3.frequency)*1000; |
305 | 338 | ||
306 | /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ | 339 | /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ |
@@ -712,3 +745,189 @@ static void UTF8ToAnsi (unsigned char *pUTF8) | |||
712 | *pAnsi = 0; | 745 | *pAnsi = 0; |
713 | } | 746 | } |
714 | 747 | ||
748 | /* This function extracts the information stored in the Vorbis comment header | ||
749 | * and stores it in id3v2buf of the current track. Currently the combined | ||
750 | * lengths of title, genre, album, and artist must be no longer than 296 bytes | ||
751 | * (the remaining 4 bytes are the null bytes at the end of the strings). This | ||
752 | * is wrong, since vorbis comments can be up to 2^32 - 1 bytes long. In | ||
753 | * practice I don't think this limitation will cause a problem. | ||
754 | * | ||
755 | * According to the docs, a vorbis bitstream *must* have a comment packet even | ||
756 | * if that packet is empty. Therefore if this function returns false the | ||
757 | * bitstream is corrupt and shouldn't be used. | ||
758 | * | ||
759 | * Additionally, vorbis comments *may* take up more than one Ogg page, and this | ||
760 | * only looks at the first page of comments. | ||
761 | */ | ||
762 | static bool get_vorbis_comments (struct mp3entry *entry, int fd) | ||
763 | { | ||
764 | int vendor_length; | ||
765 | int comment_count; | ||
766 | int comment_length; | ||
767 | int i = 0; | ||
768 | unsigned char temp[300]; | ||
769 | int buffer_remaining = sizeof(entry->id3v2buf); | ||
770 | char *buffer = entry->id3v2buf; | ||
771 | char **p = NULL; | ||
772 | int segments; | ||
773 | int packet_remaining = 0; | ||
774 | |||
775 | /* Comments are in second Ogg page */ | ||
776 | if ( lseek(fd, 58, SEEK_SET) < 0 ) { | ||
777 | return false; | ||
778 | } | ||
779 | |||
780 | /* Minimum header length for Ogg pages is 27 */ | ||
781 | if (read(fd, temp, 27) < 27) { | ||
782 | return false; | ||
783 | } | ||
784 | |||
785 | if (memcmp(temp,"OggS",4)!=0) { | ||
786 | logf("1: Not an Ogg Vorbis file"); | ||
787 | return(false); | ||
788 | } | ||
789 | |||
790 | segments=temp[26]; | ||
791 | /* read in segment table */ | ||
792 | if (read(fd, temp, segments) < segments) { | ||
793 | return false; | ||
794 | } | ||
795 | |||
796 | /* The second packet in a vorbis stream is the comment packet. It *may* | ||
797 | * extend beyond the second page, but usually does not. Here we find the | ||
798 | * length of the comment packet (or the rest of the page if the comment | ||
799 | * packet extends to the third page). | ||
800 | */ | ||
801 | for (i = 0; i < segments; i++) { | ||
802 | packet_remaining += temp[i]; | ||
803 | /* The last segment of a packet is always < 255 bytes */ | ||
804 | if (temp[i] < 255) { | ||
805 | break; | ||
806 | } | ||
807 | } | ||
808 | |||
809 | /* Now read in packet header (type and id string) */ | ||
810 | if(read(fd, temp, 7) < 7) { | ||
811 | return false; | ||
812 | } | ||
813 | |||
814 | /* The first byte of a packet is the packet type; comment packets are | ||
815 | * type 3. | ||
816 | */ | ||
817 | if ((temp[0] != 3) || (memcmp(temp + 1,"vorbis",6)!=0)) { | ||
818 | logf("Not a vorbis comment packet"); | ||
819 | return false; | ||
820 | } | ||
821 | |||
822 | packet_remaining -= 7; | ||
823 | |||
824 | |||
825 | /* We've read in all header info, now start reading comments */ | ||
826 | |||
827 | if (read(fd, &vendor_length, 4) < 4) { | ||
828 | return false; | ||
829 | } | ||
830 | little_endian_to_native(&vendor_length, "L"); | ||
831 | lseek(fd, vendor_length, SEEK_CUR); | ||
832 | |||
833 | if (read(fd, &comment_count, 4) < 4) { | ||
834 | return false; | ||
835 | } | ||
836 | little_endian_to_native(&comment_count, "L"); | ||
837 | packet_remaining -= (vendor_length + 8); | ||
838 | if ( packet_remaining <= 0 ) { | ||
839 | return true; | ||
840 | } | ||
841 | |||
842 | for ( i = 0; i < comment_count; i++ ) { | ||
843 | int name_length = 0; | ||
844 | |||
845 | if (read(fd, &comment_length, 4) < 4) { | ||
846 | return false; | ||
847 | } | ||
848 | |||
849 | little_endian_to_native(&comment_length, "L"); | ||
850 | |||
851 | /* Quit if we've passed the end of the page */ | ||
852 | packet_remaining -= (comment_length + 4); | ||
853 | if ( packet_remaining <= 0 ) { | ||
854 | return true; | ||
855 | } | ||
856 | |||
857 | /* Skip comment if it won't fit in buffer */ | ||
858 | if ( (unsigned int)comment_length >= sizeof(temp) ) { | ||
859 | lseek(fd, comment_length, SEEK_CUR); | ||
860 | continue; | ||
861 | } | ||
862 | |||
863 | if ( read(fd, temp, comment_length) < comment_length ) { | ||
864 | return false; | ||
865 | } | ||
866 | |||
867 | temp[comment_length] = '\0'; | ||
868 | UTF8ToAnsi(temp); | ||
869 | comment_length = strlen(temp); | ||
870 | |||
871 | if (strncasecmp(temp, "TITLE=", 6) == 0) { | ||
872 | name_length = 5; | ||
873 | p = &(entry->title); | ||
874 | } else if (strncasecmp(temp, "ALBUM=", 6) == 0) { | ||
875 | name_length = 5; | ||
876 | p = &(entry->album); | ||
877 | } else if (strncasecmp(temp, "ARTIST=", 7) == 0) { | ||
878 | name_length = 6; | ||
879 | p = &(entry->artist); | ||
880 | } else if (strncasecmp(temp, "GENRE=", 6) == 0) { | ||
881 | name_length = 5; | ||
882 | p = &(entry->genre_string); | ||
883 | } else if (strncasecmp(temp, "DATE=", 5) == 0) { | ||
884 | int j=0; | ||
885 | /* verify that this is a number */ | ||
886 | /* Note: vorbis uses UTF-8 for its comments, so it is | ||
887 | * safe to compare the values against ASCII 0 and 9 | ||
888 | */ | ||
889 | while ( j < (comment_length - 5) ) { | ||
890 | if ( (temp[5+j] < '0') || (temp[5+j] > '9') ) { | ||
891 | break; | ||
892 | } | ||
893 | j++; | ||
894 | } | ||
895 | if ( j == (comment_length - 5) ) { | ||
896 | p = NULL; | ||
897 | entry->year = atoi(temp + 5); | ||
898 | } | ||
899 | } else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) { | ||
900 | int j=0; | ||
901 | /* verify that this is a number */ | ||
902 | /* Note: vorbis uses UTF-8 for its comments, so it is | ||
903 | * safe to compare the values against ASCII 0 and 9 | ||
904 | */ | ||
905 | while ( j < (comment_length - 12) ) { | ||
906 | if ( (temp[12+j] < '0') || (temp[12+j] > '9') ) { | ||
907 | break; | ||
908 | } | ||
909 | j++; | ||
910 | } | ||
911 | if ( j == (comment_length - 12) ) { | ||
912 | p = NULL; | ||
913 | entry->tracknum = atoi(temp + 12); | ||
914 | } | ||
915 | } else { | ||
916 | p = NULL; | ||
917 | } | ||
918 | |||
919 | if (p) { | ||
920 | comment_length -= (name_length + 1); | ||
921 | if ( comment_length < buffer_remaining ) { | ||
922 | strncpy(buffer, temp + name_length + 1, comment_length); | ||
923 | buffer[comment_length] = '\0'; | ||
924 | *p = buffer; | ||
925 | buffer += comment_length + 1; | ||
926 | buffer_remaining -= comment_length + 1; | ||
927 | } | ||
928 | } | ||
929 | } | ||
930 | |||
931 | return true; | ||
932 | } | ||
933 | |||