summaryrefslogblamecommitdiffstatshomepage
path: root/src/lib/util/aviio.cpp
blob: 339a4de323128beb1e30e9651bf4ff53680af7af (plain) (tree)
1
2
3
4
5
6
7
8
9
10
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
                       
                                           





                                                                            

                                                                            



                  
 






                                                                            
   
                     
  
                                               

   
                                 

   
                       
  
                                                 

   

                                 
                                        
                                                                                        
                                                 
                                     

   
               
  
                                                

   
                                                        
 
   
                          
  
                                                        

   
                                 

   
                         
  
                                                   

   
                                                                                 
 
                                  
                                                           
                                  
                                                           
                                  
                                                           
                                  
                                                           
                                  
                                                           
                                  
                                                           
                                         
                                                           
                                  
                                                           
                                  
                                                             
                                  
                                                             
                                  
                                                             
                                  
                                                             
                                     

                                                               
                                
                                                           
                                 
                                                           
                                 
                                                           
                                 
                                                           
                                 

                                                           
                                   
                                                           
                                   

                                                           
                                  
                                                           
                                
                                                           
 
                           

   
                     
  
                                               

   
                                          

   
                         
  
                                                   

   
                                          

   
                          
  
                                                    

   
                                          

   
                        
  
                                                  

   
                                          

   
                           
  
                                                     

   
                                          

                       

   
                            
  
                                                      

   
                                    

   
                           
  
                                                     

   
                                    

   
                         
  
                                                   

   
                                    

   
                        
  
                                                   

   
                                    

                         

   
                            
  
                                                      

   
                                  

   
                                
  
                                                          

   
                                  

   
                              
  
                                                        

   
                                  

   
                              
  
                                                        

   
                                     

 
           



                                                                            
   
                     
  
                         

   
                
 





                                                                                              

  
   
                          
  
                               
   
 
                     
 

                                                                                              

  
   
                      
  
                          
   
 





















                                        
 









                                                               
 









                                                               
 



























































                                                                                                                                  
 














                                                                                              

                                     

                                                                                   

  
   
                         
  
                        
   
 
                                     
 











                                                                                   
 



                                                                                     
 



                                            
 


                                                          
 






                                                                  
 

































                                                                                                                                                  


                                      
                                                                                                   
         
 











































                                                                                                                                     
 




                                                                                      
 


                                                                                                    
 


                                                                                   
 

                                                                                             
 

                                                                                   
 





                                                                                                                











                                                                            
   
                                                                          
  
                               
  
                             
  
                       

   
                                                           









                                                   
   
                                                                          
  
                               
  
                             
  
                       

   
                                                           









                                                                            
   
                                                                          
  
                               
  
                             
  
                       

   
                                                           
 



                                                                                         







                                                   
   
                                                                              
  
                            
  

                                                  

   
                                                               










                                                   
   
                                                                              
  
                            
  

                                                  

   
                                                               












                                                   
   
                                                                              
  
                            
  

                                                  

   
                                                               











                              































































































                                                                                                                                          




                                                   
   
                                                    
  
                              
  
                                                    

   
                                                    
 


                                                                          
 
                       







                                                   
   
                                                                                            
  
                              
  


                                                    
  
                                                    

   
                                                                            
 
                                    

                                                                          
                 
                                                                      
                         

                                                             
                         
                                                                   

                 
                       



                                                   



                                                   
   
                                                                     
  
                                        
  
                                                  
  
                                        

   
                                                             

                       

                                     

                                                                 









                                                   
   
                                                                                              
  
                                    
  

                                                    
  
                                   

   
                                                                                          
 
                              
 



                                                                                                              










                                                   
   
                                                                                                 
  
                                  
  

                                                  
  
                             

   
                                                                                       
 
                                                                                                                                                                                                








                                                   
   
                                                                                       
  
                              
  

                                                  
  
                         

   
                                                                             

                                                                 
                                         
         

                                                        
         
                           


 
                                                   

                                               

                                                   
   
                                                                                                                                          
  
                                   
  



                                                    
  
                         

   
                                                                                                                               
 



                                                                      
 

                                    
         

                                                                             
 






                                                             
 







                                                           
 

                                                   
         






                                                                             
         

                                     



                                                   

                                              

                                                   
   
                                                                                                                                            
  
                                     
  



                                                    
  
                         

   
                                                                                                                                 
 

                                                                                                      
 

                                      
         

                                                                                                          
 

                                                
                 












                                                                         
                 
         

                                     



                                                   


















































































































































































































































































































































































































                                                                                                                                                
 
   
                                           
  
                      
  
                                                  
  
                         

   
                               
 
                                   

                                                                                           
                                      

                                                  
                                               

                                          

                                               

                                                                    

                                                             
 
                                                                     
                                                                         
                 



                                                                                    
                 

                                           

                                                         

                                          

                                               


                            
                       
 
                        






                                                   
   
                                              
  
                              
  
                                                  

   
                                   
 
                                                



                                                   
                                            


                                                   
   
                                                                
  
                                      
  
                                                  
  
                                                      

   
                                                                 
 
                      







                                                   
   
                                                                                       
  
                                      
  

                                                  
  
                             

   
                                                                                
 
                                               



                                                   
                                          



                                                   
   
                                                                                                    
  
                                 
  


                                                  
  
                         

   
                                                                                             
 
                                  


                                                      

                                                     

                                                                                                                                                     

                                           

                                            

                                                       

                                                                                   

                                                                 


                                                                   


                              



                                                                                                                                                  

                                        

                                                                     

                                        

                                                                                                                                   


                                          
                                                                                                                               

            
                                             









                                                   
   
                                                                                                                                                
  
                                   
  




                                                    
  
                         

   
                                                                                                                                         
 
                                  



                                                                     

                                                     

                                                                                               

                                    



                                                             

                                        
                                                                          



                                                        

                                                                   


                                                            
                                                                           
                 
                                                                                                       




                                                   
                                                                   
                                                 
                 
                                                               


                              
                                                                         

                                                                                 


                                      


                                                                                                                                               

                                                


                                                              


                                                            
                                                                                

                                                           
                                               
                 

                                                                                                            


                                                                                  
                                                           



                                                          
                                                   
                 

                                                                                        


                                                                                  
                                                           
                         
                 
 



                                                  
 
                           



                                                   

                                                 

                                                   
   
                                                                              
  
                                   
  
                                                  
                                       
  
                         

   
                                                                       
 


                                                      
 


                                                                                                                                                     
 



                                                        
 




                                                           
 



                                                                                    
 



                                                                                          
 



                                                                                                      
 
                                                                        
 
                           



                                                   

                                                 

                                                   
   
                                                                              
  
                                   
  
                                                  
                                       
  
                         

   
                                                                       
 


                                                      
 


                                                       
 
































                                                                                                      



                                                   

                                           

                                                   
   
                                                                                                                                                        
  
                                     
  




                                                  
  
                         

   
                                                                                                                                                 
 

                                                                   
 


                                                         
 

                                                                  
         



                                                                                  
         
                                                     
 

                                                 



                                                   

                                              

                                                   
   
                                                                                                       
  
                             
  
                                                  

                                                    
  
                           

   
                                                                                                                
 


                                                           
 



                                                                                                          
         

                                         

         
                           



                                                   

                                               

                                                   
   
                                                                                                      
  
                                 
  


                                                          
  
                            

   
                                                                                            
 



                                                                                                                       



                                                   

                                              

                                                   
   
                                                                                                     
  
                                
  


                                                          
  
                           

   
                                                                                           
 

                                                                                             



                                                   

                                                   

                                                   
   
                                                                                                                              
  
                                         
  
                                                  


                                                    
  
                            

   


                                                                                                                    
 


                                                                                                                            
 




                                                   

                                                  

                                                   
   
                                                                                                                             
  
                                        
  
                                                  


                                                    
  
                            

   
                                                                                                                   
 
                     
 


                                                                                                                           
 




                                                   

                                                  

                                                   
   
                                                                                                                             
  
                                        
  
                                                  


                                                    
  
                           

   
                                                                                                                   
 
                     
 


                                                                                                                                                              
 




                                                   

                                                 

                                                   
   
                                                                                                                            
  
                                       
  
                                                  


                                                    
  
                           

   
                                                                                                                  
 
                     
 


                                                                                                                                                             
 




                                                   

                                            

                                                   
   
                                                                                                                                    
  
                                         
  



                                                          
  
                                    

   
                                                                                                                          
 


                                
 


                                          
 

                                         
 


                                                                     
 



                                                                     
 


                                                 
 

                                                                               
         



                                                                                     

         
                           



                                                   
                                            

                                                   
   
                                                        
  
                             
  
                                                  
  
                           

   
                                                
 

                                                                       
 



                                                                 
 
























                                                                                                                                              
         






















                                                                       
 






                                                                             

         
















                                                                        
 


                                        



                                                   

                                               

                                                   
   
                                                           
  
                                                             
  
                                                  
  
                                            

   
                                                   
 

                           
 


                                    
         







                                                            
 











                                                               

         




                                                                                     
 








                                                                          
 
                           



                                                   

                                           

                                                   
   
                                                                          
  
                             
  
                                                  
                                                  
  
                         

   
                                                                      
 




                                                        
 

                                                                   
 


                                             





                                                   

                                          

                                                   
   
                                                                                              
  
                             
  
                                                  

                                                    
  
                         

   
                                                                                          
 



                                                              

                              

                                                              



                                                   
                                          


                                                   
   
                                                                                              
  
                             
  
                                                  

                                                    
  
                         

   
                                                                                          
 
                     
 




                                                   
 
                                                              



                                                   
                                          

                                                   
   
                                                                                              
  
                             
  

                                                    
                                                  
  
                         

   
                                                                                          
 
                     
 




                                                   
 






                                                                          
 

                                                                                       
         






























                                                                                                   

         

                                                 
         















                                                                                                              

         
                      



                                                   
                                          

                                                   
   
                                                                                                    
  
                                
  
                                                  

                                                         
  
                         

   
                                                                                                
 
                     
 




                                                   
 


                                                               
         




                                                                        
 





                                                           
 



                                                                                               

         
                      



                                                   
                                             

                                                   
   
                                                                                                                        
  
                                          
  
                                                  


                                          
  
                         

   
                                                                                                              
 



                                                     
 




                                          
 



                                                             
 













                                                                                                           
         
                                        
 



                                                       
 






                                                                                                           
 

                           
 
 


                                                   
 













                                                                         
 


                                                  
 

                                                                    
         






                                                                                                                

         



                                       



                                                   
                                             

                                                   
   
                                                                                                                
  
                        
  
                                                  


                                       
  
                         

   
                                                                                                      
 
                     
 

                                                                                                               
 



                                                                                                               
         



                                          
 






                                                               
 



                                          
 










                                                                      

         













                                                                                         



                                                   



                                                  

                                                   
   
                                                                                                                                                              
  
                            
  
                                                  




                                                    
  
                         

   
                                                                                                                                                     
 
                                      
 


                                                           
 





                                                                                           
 

                                                             
 


                                                                          
 
                      



                                                   

                                                

                                                   
   
                                                              
  
                                      
  
                                                  
  
                         

   
                                                      
 
                     
 

                                     
 



                                                             
 


                                                              

                              













































                                                                      



                                                   

                                            

                                                   
   
                                                                            
  
                                 
  
                                                  
                                              
  
                         

   
                                                                   
 

                                               
 

                                               
 





                                                                                                                
 


                                                                                                           
 
 



                                                   
 










                                                                                                
 


                                                                                       
 

                                               
 




                                                                           
 








                                                                                                       

         

                                             
         



                                                                                           

         

                                                                                                                  



                                                   

                                            

                                                   
   
                                                                             
  
                                
  
                                                  
                                                    
  
                         

   
                                                                         
 

                                             
         
                                        
 

                                                       
 










                                                                                                     
         
 

                                             
         















                                                                                         

         
                                   



                                                   

                                            

                                                   
   
                                                                                                
  
                                 
  
                                                  
                                                    
                                              
  
                         

   
                                                                                       
 

                                                              
 

                                               
 







                                                                                                                                            
                 

























                                                                                                                               
 



                                                                                                                               
                                 



                                                                                                                                              
                                 













                                                                                                                                 


                 











                                                                                                                                                           



                                                   
                                           

                                                   
   
                                                         
  
                                    
  
                                                  
  
                         

   
                                                 
 

                                                                    
 






                                                                                     
         

                                                            
 






                                                                                                                                          
 







                                                                                                           

         

                                                                       



                                                   

                                              

                                                   
   
                                                            
  
                                
  
                                                  
  
                         

   
                                                    
 


                                                              
 


                                                       
 


                                     
 

                                                                                               
 



                                                                                 
 


                                                                                                            



                                                   

                                            

                                                   
   
                                                                                     
  
                                 
  

                                                  
  
                         

   
                                                                           
 


                                                               
 


                                     
 





                                                                                                                                       
 



                                                                                                 
 


                                                                                              
 
 



                                                   
 









                                                                            
 









                                                                   
 


                                   
 



                                                                                         
 


                                                                                                           
 




                                                                                         
 


                                                                        
 

                                                                            
                 



                                                                                                                                                                  
 







                                                                                                                            
 



                                                                                                                                             
 




                                                                                      
 



                                                               

         







                                                                                                                                                             
 


                                                
 


                                        







                                                   
   
                                                                                            
  
                                   
  


                                                           

   
                                                                                  


                                  
                     

                                                   
                                                                                                                                
         

                                                        



                                                                         



                                                                  








                                                                           




                                                                                       






                                                                 
                                                              

                                                       

                                                                 
                         

                                                                                                      





                                                                                      




                                                   
                                                                      


                                                                







































































































































































                                                                                                                                                   
 
// license:BSD-3-Clause
// copyright-holders:Aaron Giles, Vas Crabb
/***************************************************************************

    aviio.c

    AVI movie format parsing helpers.

***************************************************************************/

#include <array>
#include <cassert>
#include <cstdlib>
#include <cstring>

#include "aviio.h"


/***************************************************************************
    CONSTANTS
***************************************************************************/

/**
 * @def FILETYPE_READ
 *
 * @brief   A macro that defines filetype read.
 */

#define FILETYPE_READ           1

/**
 * @def FILETYPE_CREATE
 *
 * @brief   A macro that defines filetype create.
 */

#define FILETYPE_CREATE         2

/** @brief  Size of the maximum riff. */
#define MAX_RIFF_SIZE           (2UL * 1024 * 1024 * 1024 - 1024)   /* just under 2GB */
/** @brief  The maximum avi size in gigabytes. */
#define MAX_AVI_SIZE_IN_GB      (256)

/**
 * @def FOUR_GB
 *
 * @brief   A macro that defines four gigabytes.
 */

#define FOUR_GB                 (std::uint64_t(1) << 32)

/**
 * @def MAX_SOUND_CHANNELS
 *
 * @brief   A macro that defines maximum sound channels.
 */

#define MAX_SOUND_CHANNELS      2

/**
 * @def SOUND_BUFFER_MSEC
 *
 * @brief   A macro that defines sound buffer msec.
 */

#define SOUND_BUFFER_MSEC       2000        /* milliseconds of sound buffering */

/** @brief  The chunktype riff. */
#define CHUNKTYPE_RIFF          AVI_FOURCC('R','I','F','F')
/** @brief  List of chunktypes. */
#define CHUNKTYPE_LIST          AVI_FOURCC('L','I','S','T')
/** @brief  The chunktype junk. */
#define CHUNKTYPE_JUNK          AVI_FOURCC('J','U','N','K')
/** @brief  The chunktype avih. */
#define CHUNKTYPE_AVIH          AVI_FOURCC('a','v','i','h')
/** @brief  The chunktype strh. */
#define CHUNKTYPE_STRH          AVI_FOURCC('s','t','r','h')
/** @brief  The chunktype strf. */
#define CHUNKTYPE_STRF          AVI_FOURCC('s','t','r','f')
/** @brief  The first chunktype index. */
#define CHUNKTYPE_IDX1          AVI_FOURCC('i','d','x','1')
/** @brief  The chunktype indx. */
#define CHUNKTYPE_INDX          AVI_FOURCC('i','n','d','x')
/** @brief  The chunktype xxdb. */
#define CHUNKTYPE_XXDB          AVI_FOURCC(0x00,0x00,'d','b')
/** @brief  The chunktype xxdc. */
#define CHUNKTYPE_XXDC          AVI_FOURCC(0x00,0x00,'d','c')
/** @brief  The chunktype xxwb. */
#define CHUNKTYPE_XXWB          AVI_FOURCC(0x00,0x00,'w','b')
/** @brief  The chunktype ixxx. */
#define CHUNKTYPE_IXXX          AVI_FOURCC('i','x',0x00,0x00)
/** @brief  The chunktype xx mask. */
#define CHUNKTYPE_XX_MASK       AVI_FOURCC(0x00,0x00,0xff,0xff)

/** @brief  The listtype avi. */
#define LISTTYPE_AVI            AVI_FOURCC('A','V','I',' ')
/** @brief  The listtype avix. */
#define LISTTYPE_AVIX           AVI_FOURCC('A','V','I','X')
/** @brief  The listtype hdrl. */
#define LISTTYPE_HDRL           AVI_FOURCC('h','d','r','l')
/** @brief  The listtype strl. */
#define LISTTYPE_STRL           AVI_FOURCC('s','t','r','l')
/** @brief  The listtype movi. */
#define LISTTYPE_MOVI           AVI_FOURCC('m','o','v','i')

/** @brief  The streamtype vids. */
#define STREAMTYPE_VIDS         AVI_FOURCC('v','i','d','s')
/** @brief  The streamtype auds. */
#define STREAMTYPE_AUDS         AVI_FOURCC('a','u','d','s')

/** @brief  The handler bitmap. */
#define HANDLER_DIB             AVI_FOURCC('D','I','B',' ')
/** @brief  The handler hfyu. */
#define HANDLER_HFYU            AVI_FOURCC('h','f','y','u')

/* main AVI header files */

/**
 * @def AVIF_HASINDEX
 *
 * @brief   A macro that defines avif hasindex.
 */

#define AVIF_HASINDEX           0x00000010

/**
 * @def AVIF_MUSTUSEINDEX
 *
 * @brief   A macro that defines avif mustuseindex.
 */

#define AVIF_MUSTUSEINDEX       0x00000020

/**
 * @def AVIF_ISINTERLEAVED
 *
 * @brief   A macro that defines avif isinterleaved.
 */

#define AVIF_ISINTERLEAVED      0x00000100

/**
 * @def AVIF_COPYRIGHTED
 *
 * @brief   A macro that defines avif copyrighted.
 */

#define AVIF_COPYRIGHTED        0x00010000

/**
 * @def AVIF_WASCAPTUREFILE
 *
 * @brief   A macro that defines avif wascapturefile.
 */

#define AVIF_WASCAPTUREFILE     0x00020000

/* index definitions */

/**
 * @def AVI_INDEX_OF_INDEXES
 *
 * @brief   A macro that defines avi index of indexes.
 */

#define AVI_INDEX_OF_INDEXES    0x00

/**
 * @def AVI_INDEX_OF_CHUNKS
 *
 * @brief   A macro that defines avi index of chunks.
 */

#define AVI_INDEX_OF_CHUNKS     0x01

/**
 * @def AVI_INDEX_IS_DATA
 *
 * @brief   A macro that defines avi index is data.
 */

#define AVI_INDEX_IS_DATA       0x80

/**
 * @def AVI_INDEX_2FIELD
 *
 * @brief   A macro that defines avi index 2 field.
 */

#define AVI_INDEX_2FIELD        0x01

/* HuffYUV definitions */

/**
 * @def HUFFYUV_PREDICT_LEFT
 *
 * @brief   A macro that defines huffyuv predict left.
 */

#define HUFFYUV_PREDICT_LEFT     0

/**
 * @def HUFFYUV_PREDICT_GRADIENT
 *
 * @brief   A macro that defines huffyuv predict gradient.
 */

#define HUFFYUV_PREDICT_GRADIENT 1

/**
 * @def HUFFYUV_PREDICT_MEDIAN
 *
 * @brief   A macro that defines huffyuv predict median.
 */

#define HUFFYUV_PREDICT_MEDIAN   2

/**
 * @def HUFFYUV_PREDICT_DECORR
 *
 * @brief   A macro that defines huffyuv predict decorr.
 */

#define HUFFYUV_PREDICT_DECORR   0x40


namespace {
/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/**
 * @struct  avi_chunk
 *
 * @brief   An avi chunk.
 */

struct avi_chunk
{
	avi_chunk() : offset(0), size(0), type(0), listtype(0) { }

	std::uint64_t       offset;                 /* file offset of chunk header */
	std::uint64_t       size;                   /* size of this chunk */
	std::uint32_t       type;                   /* type of this chunk */
	std::uint32_t       listtype;               /* type of this list (if we are a list) */
};

/**
 * @struct  avi_chunk_list
 *
 * @brief   List of avi chunks.
 */

struct avi_chunk_list
{
	std::uint64_t       offset;                 /* offset in the file of header */
	std::uint32_t       length;                 /* length of the chunk including header */
};

/**
 * @struct  avi_stream
 *
 * @brief   An avi stream.
 */

class avi_stream
{
public:
	avi_stream()
		: m_type(0)
		, m_format(0)
		, m_rate(0)
		, m_scale(0)
		, m_samples(0)
		, m_chunks()
		, m_width(0)
		, m_height(0)
		, m_depth(0)
		, m_interlace(0)
		, m_huffyuv()
		, m_channels(0)
		, m_samplebits(0)
		, m_samplerate(0)
		, m_saved_strh_offset(0)
		, m_saved_indx_offset(0)
	{
	}

	void initialize_video(avi_file::movie_info const &info)
	{
		m_type = STREAMTYPE_VIDS;
		m_format = info.video_format;
		m_rate = info.video_timescale;
		m_scale = info.video_sampletime;
		m_width = info.video_width;
		m_height = info.video_height;
		m_depth = info.video_depth;
	}

	void initialize_audio(avi_file::movie_info const &info)
	{
		m_type = STREAMTYPE_AUDS;
		m_format = info.audio_format;
		m_rate = info.audio_timescale;
		m_scale = info.audio_sampletime;
		m_channels = info.audio_channels;
		m_samplebits = info.audio_samplebits;
		m_samplerate = info.audio_samplerate;
	}

	std::uint32_t type() const { return m_type; }
	std::uint32_t format() const { return m_format; }

	std::uint32_t rate() const { return m_rate; }
	std::uint32_t scale() const { return m_scale; }
	std::uint32_t samples() const { return m_samples; }
	void set_samples(std::uint32_t value) { m_samples = value; }
	std::uint32_t add_samples(std::uint32_t value) { return m_samples += value; }

	std::uint32_t chunks() const { return m_chunks.size(); }
	avi_chunk_list const &chunk(std::uint32_t index) const { assert(index < m_chunks.size()); return m_chunks[index]; }
	avi_chunk_list &chunk(std::uint32_t index) { assert(index < m_chunks.size()); return m_chunks[index]; }
	avi_file::error set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length);
	void set_chunks(std::uint32_t count) { assert(count <= m_chunks.size()); m_chunks.resize(count); }

	std::uint32_t width() const { return m_width; }
	std::uint32_t height() const { return m_height; }
	std::uint32_t depth() const { return m_depth; }

	std::uint16_t channels() const { return m_channels; }
	std::uint16_t samplebits() const { return m_samplebits; }
	std::uint32_t samplerate() const { return m_samplerate; }
	std::uint32_t bytes_per_sample() const { return (m_samplebits / 8) * m_channels; }

	avi_file::error set_strh_data(std::uint8_t const *data, std::uint32_t size);
	avi_file::error set_strf_data(std::uint8_t const *data, std::uint32_t size);

	std::uint64_t &saved_strh_offset() { return m_saved_strh_offset; }
	std::uint64_t &saved_indx_offset() { return m_saved_indx_offset; }

	// RGB helpers
	avi_file::error rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;

	// YUY helpers
	avi_file::error yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;
	avi_file::error yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;

	// HuffYUV helpers
	avi_file::error huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;

private:
	struct huffyuv_table
	{
		std::uint8_t        shift[256];             /* bit shift amounts */
		std::uint32_t       bits[256];              /* bit match values */
		std::uint32_t       mask[256];              /* bit mask values */
		std::uint16_t       baselookup[65536];      /* base lookup table */
		std::vector<std::uint16_t> extralookup;     /* extra lookup tables */
	};

	struct huffyuv_data
	{
		std::uint8_t        predictor;              /* predictor */
		huffyuv_table       table[3];               /* array of tables */
	};

	avi_file::error huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size);

	std::uint32_t       m_type;                 /* subtype of stream */
	std::uint32_t       m_format;               /* format of stream data */

	std::uint32_t       m_rate;                 /* timescale for stream */
	std::uint32_t       m_scale;                /* duration of one sample in the stream */
	std::uint32_t       m_samples;              /* number of samples */

	std::vector<avi_chunk_list> m_chunks;       /* list of chunks */

	std::uint32_t       m_width;                /* width of video */
	std::uint32_t       m_height;               /* height of video */
	std::uint32_t       m_depth;                /* depth of video */
	std::uint8_t        m_interlace;            /* interlace parameters */
	std::unique_ptr<huffyuv_data const> m_huffyuv; /* huffyuv decompression data */

	std::uint16_t       m_channels;             /* audio channels */
	std::uint16_t       m_samplebits;           /* audio bits per sample */
	std::uint32_t       m_samplerate;           /* audio sample rate */

	/* only used when creating */
	std::uint64_t       m_saved_strh_offset;    /* writeoffset of strh chunk */
	std::uint64_t       m_saved_indx_offset;    /* writeoffset of indx chunk */
};

/**
 * @class   avi_file_impl
 *
 * @brief   An avi file.
 */

class avi_file_impl : public avi_file
{
public:
	avi_file_impl(osd_file::ptr &&file, std::uint64_t length) : avi_file_impl()
	{
		m_file = std::move(file);
		m_type = FILETYPE_READ;

		// make a root atom
		m_rootchunk.offset = 0;
		m_rootchunk.size = length;
		m_rootchunk.type = 0;
		m_rootchunk.listtype = 0;
	}

	avi_file_impl(osd_file::ptr &&file, movie_info const &info) : avi_file_impl()
	{
		m_file = std::move(file);
		m_type = FILETYPE_CREATE;

		// copy the movie info
		m_info = info;
		m_info.video_numsamples = 0;
		m_info.audio_numsamples = 0;

		// initialize the video track
		m_streams.emplace_back();
		m_streams.back().initialize_video(m_info);

		// initialize the audio track
		if (m_info.audio_channels > 0)
		{
			m_streams.emplace_back();
			m_streams.back().initialize_audio(m_info);
		}
	}

	virtual ~avi_file_impl() override;

	virtual void printf_chunks() override;

	virtual movie_info const &get_movie_info() const override;
	virtual std::uint32_t first_sample_in_frame(std::uint32_t framenum) const override;

	virtual error read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap) override;
	virtual error read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) override;

	virtual error append_video_frame(bitmap_yuy16 &bitmap) override;
	virtual error append_video_frame(bitmap_rgb32 &bitmap) override;
	virtual error append_sound_samples(int channel, std::int16_t const *samples, std::uint32_t numsamples, std::uint32_t sampleskip) override;

	error read_movie_data();
	error write_initial_headers();
	error soundbuf_initialize();

private:
	avi_file_impl()
		: m_file()
		, m_type(0)
		, m_info({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })
		, m_tempbuffer()
		, m_streams()
		, m_rootchunk()
		, m_writeoffs(0)
		, m_riffbase(0)
		, m_chunkstack()
		, m_chunksp(0)
		, m_saved_movi_offset(0)
		, m_saved_avih_offset(0)
		, m_soundbuf()
		, m_soundbuf_samples(0)
		, m_soundbuf_chunks(0)
		, m_soundbuf_frames(0)
	{
		std::fill(std::begin(m_soundbuf_chansamples), std::end(m_soundbuf_chansamples), 0);
	}

	avi_stream *get_video_stream();
	avi_stream *get_audio_stream(int channel, int &offset);
	std::uint32_t compute_idx1_size() const;
	std::uint32_t get_chunkid_for_stream(const avi_stream *stream) const;
	std::uint32_t framenum_to_samplenum(std::uint32_t framenum) const;
	error expand_tempbuffer(std::uint32_t length);

	// core chunk read routines
	error get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk);
	error get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk);
	error find_first_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_next_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_first_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error find_next_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
	error get_next_chunk_internal(avi_chunk const *parent, avi_chunk &newchunk, std::uint64_t offset);
	error read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer);

	// chunk read helpers
	error extract_movie_info();
	error parse_avih_chunk(avi_chunk const &avih);
	error parse_strh_chunk(avi_stream &stream, avi_chunk const &strh);
	error parse_strf_chunk(avi_stream &stream, avi_chunk const &strf);
	error parse_indx_chunk(avi_stream &stream, avi_chunk const &strf);
	error parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1);

	// core chunk write routines
	error chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength);
	error chunk_close();
	error chunk_write(std::uint32_t type, const void *data, std::uint32_t length);
	error chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write);

	// chunk write helpers
	error write_avih_chunk(bool initial_write);
	error write_strh_chunk(avi_stream &stream, bool initial_write);
	error write_strf_chunk(avi_stream const &stream);
	error write_indx_chunk(avi_stream &stream, bool initial_write);
	error write_idx1_chunk();

	// sound buffering helpers
	error soundbuf_write_chunk(std::uint32_t framenum);
	error soundbuf_flush(bool only_flush_full);

	// debugging
	void printf_chunk_recursive(avi_chunk const *chunk, int indent);

	/* shared data */
	osd_file::ptr       m_file;                 /* pointer to open file */
	int                 m_type;                 /* type of access (read/create) */
	movie_info          m_info;                 /* movie info structure */
	std::vector<std::uint8_t> m_tempbuffer;     /* temporary buffer */

	/* only used when reading */
	std::vector<avi_stream> m_streams;          /* allocated array of stream information */
	avi_chunk           m_rootchunk;            /* dummy root chunk that wraps the whole file */

	/* only used when creating */
	std::uint64_t       m_writeoffs;            /* current file write offset */
	std::uint64_t       m_riffbase;             /* base of the current RIFF */

	std::array<avi_chunk, 8> m_chunkstack;      /* stack of chunks we are writing */
	int                 m_chunksp;              /* stack pointer for the current chunk */

	std::uint64_t       m_saved_movi_offset;    /* writeoffset of movi list */
	std::uint64_t       m_saved_avih_offset;    /* writeoffset of avih chunk */

	std::vector<std::int16_t> m_soundbuf;       /* buffer for sound data */
	std::uint32_t       m_soundbuf_samples;     /* length of sound buffer in samples */
	std::uint32_t       m_soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */
	std::uint32_t       m_soundbuf_chunks;      /* number of chunks completed so far */
	std::uint32_t       m_soundbuf_frames;      /* number of frames ahead of the video */
};



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    fetch_16bits - read 16 bits in LSB order
    from the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline std::uint16_t fetch_16bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 16bits.
 *
 * @param   data    The data.
 *
 * @return  The 16bits.
 */

inline std::uint16_t fetch_16bits(const std::uint8_t *data)
{
	return data[0] | (data[1] << 8);
}


/*-------------------------------------------------
    fetch_32bits - read 32 bits in LSB order
    from the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline std::uint32_t fetch_32bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 32bits.
 *
 * @param   data    The data.
 *
 * @return  The 32bits.
 */

inline std::uint32_t fetch_32bits(const std::uint8_t *data)
{
	return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
}


/*-------------------------------------------------
    fetch_64bits - read 64 bits in LSB order
    from the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline std::uint64_t fetch_64bits(const std::uint8_t *data)
 *
 * @brief   Fetches the 64bits.
 *
 * @param   data    The data.
 *
 * @return  The 64bits.
 */

inline std::uint64_t fetch_64bits(const std::uint8_t *data)
{
	return std::uint64_t(data[0]) | (std::uint64_t(data[1]) << 8) |
			(std::uint64_t(data[2]) << 16) | (std::uint64_t(data[3]) << 24) |
			(std::uint64_t(data[4]) << 32) | (std::uint64_t(data[5]) << 40) |
			(std::uint64_t(data[6]) << 48) | (std::uint64_t(data[7]) << 56);
}


/*-------------------------------------------------
    put_16bits - write 16 bits in LSB order
    to the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline void put_16bits(std::uint8_t *data, std::uint16_t value)
 *
 * @brief   Puts the 16bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_16bits(std::uint8_t *data, std::uint16_t value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
}


/*-------------------------------------------------
    put_32bits - write 32 bits in LSB order
    to the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline void put_32bits(std::uint8_t *data, std::uint32_t value)
 *
 * @brief   Puts the 32bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_32bits(std::uint8_t *data, std::uint32_t value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
	data[2] = value >> 16;
	data[3] = value >> 24;
}


/*-------------------------------------------------
    put_64bits - write 64 bits in LSB order
    to the given buffer
-------------------------------------------------*/

/**
 * @fn  static inline void put_64bits(std::uint8_t *data, std::uint64_t value)
 *
 * @brief   Puts the 64bits.
 *
 * @param [in,out]  data    If non-null, the data.
 * @param   value           The value.
 */

inline void put_64bits(std::uint8_t *data, std::uint64_t value)
{
	data[0] = value >> 0;
	data[1] = value >> 8;
	data[2] = value >> 16;
	data[3] = value >> 24;
	data[4] = value >> 32;
	data[5] = value >> 40;
	data[6] = value >> 48;
	data[7] = value >> 56;
}


/**
 * @fn  static void u64toa(std::uint64_t val, char *output)
 *
 * @brief   64toas.
 *
 * @param   val             The value.
 * @param [in,out]  output  If non-null, the output.
 */

inline void u64toa(std::uint64_t val, char *output)
{
	std::uint32_t lo = std::uint32_t(val & 0xffffffff);
	std::uint32_t hi = std::uint32_t(val >> 32);
	if (hi != 0)
		sprintf(output, "%X%08X", hi, lo);
	else
		sprintf(output, "%X", lo);
}


/*-------------------------------------------------
    set_stream_chunk_info - set the chunk info
    for a given chunk within a stream
-------------------------------------------------*/

/**
 * @fn  static inline avi_error set_stream_chunk_info(avi_stream *stream, std::uint32_t index, std::uint64_t offset, std::uint32_t length)
 *
 * @brief   Sets stream chunk information.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   index           Zero-based index of the.
 * @param   offset          The offset.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

inline avi_file::error avi_stream::set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length)
{
	/* if we need to allocate more, allocate more */
	if (index >= m_chunks.capacity())
	{
		try { m_chunks.reserve((std::max<std::size_t>)(index, m_chunks.capacity() + 1000)); }
		catch (...) { return avi_file::error::NO_MEMORY; }
	}

	/* update the number of chunks */
	m_chunks.resize((std::max<std::size_t>)(m_chunks.size(), index + 1));

	/* set the data */
	m_chunks[index].offset = offset;
	m_chunks[index].length = length;

	return avi_file::error::NONE;
}


inline avi_file::error avi_stream::set_strh_data(std::uint8_t const *data, std::uint32_t size)
{
	m_type = fetch_32bits(&data[0]);
	m_scale = fetch_32bits(&data[20]);
	m_rate = fetch_32bits(&data[24]);
	m_samples = fetch_32bits(&data[32]);
	return avi_file::error::NONE;
}


inline avi_file::error avi_stream::set_strf_data(std::uint8_t const *data, std::uint32_t size)
{
	/* audio and video streams have differing headers */
	if (m_type == STREAMTYPE_VIDS)
	{
		m_width = fetch_32bits(&data[4]);
		m_height = fetch_32bits(&data[8]);
		m_depth = fetch_16bits(&data[14]);
		m_format = fetch_32bits(&data[16]);

		/* extra extraction for HuffYUV data */
		if ((m_format == FORMAT_HFYU) && (size >= 56))
		{
			avi_file::error const avierr = huffyuv_extract_tables(data, size);
			if (avierr != avi_file::error::NONE)
				return avierr;
		}
	}
	else if (m_type == STREAMTYPE_AUDS)
	{
		m_channels = fetch_16bits(&data[2]);
		m_samplebits = fetch_16bits(&data[14]);
		m_samplerate = fetch_32bits(&data[4]);
	}
	return avi_file::error::NONE;
}


/*-------------------------------------------------
    get_video_stream - return a pointer to the
    video stream
-------------------------------------------------*/

/**
 * @fn  static inline avi_stream *get_video_stream()
 *
 * @brief   Gets video stream.
 *
 * @return  null if it fails, else the video stream.
 */

inline avi_stream *avi_file_impl::get_video_stream()
{
	for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
		if (m_streams[streamnum].type() == STREAMTYPE_VIDS)
			return &m_streams[streamnum];

	return nullptr;
}


/*-------------------------------------------------
    get_audio_stream - return a pointer to the
    audio stream for the 'n'th channel
-------------------------------------------------*/

/**
 * @fn  static inline avi_stream *get_audio_stream(avi_file *file, int channel, int *offset)
 *
 * @brief   Gets audio stream.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param [in,out]  offset  If non-null, the offset.
 *
 * @return  null if it fails, else the audio stream.
 */

inline avi_stream *avi_file_impl::get_audio_stream(int channel, int &offset)
{
	/* find the audios stream */
	for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
		if (m_streams[streamnum].type() == STREAMTYPE_AUDS)
		{
			if (channel < m_streams[streamnum].channels())
			{
				offset = channel;
				return &m_streams[streamnum];
			}
			channel -= m_streams[streamnum].channels();
		}

	return nullptr;
}


/*-------------------------------------------------
    compute_idx1_size - compute the size of the
    idx1 chunk
-------------------------------------------------*/

/**
 * @fn  static inline std::uint32_t compute_idx1_size(avi_file *file)
 *
 * @brief   Calculates the index 1 size.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The calculated index 1 size.
 */

inline std::uint32_t avi_file_impl::compute_idx1_size() const
{
	int chunks = 0;

	/* count chunks in streams */
	for (int strnum = 0; strnum < m_streams.size(); strnum++)
		chunks += m_streams[strnum].chunks();

	return chunks * 16 + 8;
}


/*-------------------------------------------------
    get_chunkid_for_stream - make a chunk id for
    a given stream
-------------------------------------------------*/

/**
 * @fn  static inline std::uint32_t get_chunkid_for_stream(avi_file *file, avi_stream *stream)
 *
 * @brief   Gets chunkid for stream.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 *
 * @return  The chunkid for stream.
 */

inline std::uint32_t avi_file_impl::get_chunkid_for_stream(const avi_stream *stream) const
{
	std::uint32_t chunkid;

	chunkid = AVI_FOURCC('0' + (stream - &m_streams[0]) / 10, '0' +  (stream - &m_streams[0]) % 10, 0, 0);
	if (stream->type() == STREAMTYPE_VIDS)
		chunkid |= (stream->format() == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC;
	else if (stream->type() == STREAMTYPE_AUDS)
		chunkid |= CHUNKTYPE_XXWB;

	return chunkid;
}


/*-------------------------------------------------
    framenum_to_samplenum - given a video frame
    number, get the first sample number
-------------------------------------------------*/

/**
 * @fn  static inline std::uint32_t framenum_to_samplenum(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Framenum to samplenum.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An std::uint32_t.
 */

inline std::uint32_t avi_file_impl::framenum_to_samplenum(std::uint32_t framenum) const
{
	return (std::uint64_t(m_info.audio_samplerate) * std::uint64_t(framenum) * std::uint64_t(m_info.video_sampletime) + m_info.video_timescale - 1) / std::uint64_t(m_info.video_timescale);
}


/*-------------------------------------------------
    expand_tempbuffer - expand the file's
    tempbuffer if necessary to contain the
    requested amount of data
-------------------------------------------------*/

/**
 * @fn  static inline avi_error expand_tempbuffer(avi_file *file, std::uint32_t length)
 *
 * @brief   Expand tempbuffer.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

inline avi_file::error avi_file_impl::expand_tempbuffer(std::uint32_t length)
{
	/* expand the tempbuffer to hold the data if necessary */
	if (length > m_tempbuffer.size())
	{
		try { m_tempbuffer.resize(2 * length); }
		catch (...) { return error::NO_MEMORY; }
	}
	return error::NONE;
}


/*-------------------------------------------------
    rgb32_compress_to_rgb - "compress" an RGB32
    bitmap to an RGB encoded frame
-------------------------------------------------*/

/**
 * @fn  static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
 *
 * @brief   RGB 32 compress to RGB.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   bitmap          The bitmap.
 * @param [in,out]  data    If non-null, the data.
 * @param   numbytes        The numbytes.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
{
	int const height = (std::min<int>)(m_height, bitmap.height());
	int const width = (std::min<int>)(m_width, bitmap.width());
	std::uint8_t *const dataend = data + numbytes;
	int x, y;

	/* compressed video */
	for (y = 0; y < height; y++)
	{
		const std::uint32_t *source = &bitmap.pix32(y);
		std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;

		for (x = 0; x < width && dest < dataend; x++)
		{
			rgb_t pix = *source++;
			*dest++ = pix.b();
			*dest++ = pix.g();
			*dest++ = pix.r();
		}

		/* fill in any blank space on the right */
		for ( ; x < m_width && dest < dataend; x++)
		{
			*dest++ = 0;
			*dest++ = 0;
			*dest++ = 0;
		}
	}

	/* fill in any blank space on the bottom */
	for ( ; y < m_height; y++)
	{
		std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;
		for (x = 0; x < m_width && dest < dataend; x++)
		{
			*dest++ = 0;
			*dest++ = 0;
			*dest++ = 0;
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    yuv_decompress_to_yuy16 - decompress a YUV
    encoded frame to a YUY16 bitmap
-------------------------------------------------*/

/**
 * @fn  static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
 *
 * @brief   Yuv decompress to yuy 16.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   data            The data.
 * @param   numbytes        The numbytes.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
{
	std::uint16_t const *const dataend = reinterpret_cast<const std::uint16_t *>(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		const std::uint16_t *source = reinterpret_cast<const std::uint16_t *>(data) + y * m_width;
		std::uint16_t *dest = &bitmap.pix16(y);

		/* switch off the compression */
		switch (m_format)
		{
		case FORMAT_UYVY:
			for (x = 0; x < m_width && source < dataend; x++)
				*dest++ = *source++;
			break;

		case FORMAT_VYUY:
		case FORMAT_YUY2:
			for (x = 0; x < m_width && source < dataend; x++)
			{
				std::uint16_t pix = *source++;
				*dest++ = (pix >> 8) | (pix << 8);
			}
			break;
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    yuy16_compress_to_yuy - "compress" a YUY16
    bitmap to a YUV encoded frame
-------------------------------------------------*/

/**
 * @fn  static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
 *
 * @brief   Yuy 16 compress to yuy.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   bitmap          The bitmap.
 * @param [in,out]  data    If non-null, the data.
 * @param   numbytes        The numbytes.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
{
	std::uint16_t *const dataend = reinterpret_cast<std::uint16_t *>(data + numbytes);
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		const std::uint16_t *source = &bitmap.pix16(y);
		std::uint16_t *dest = reinterpret_cast<std::uint16_t *>(data) + y * m_width;

		/* switch off the compression */
		switch (m_format)
		{
		case FORMAT_UYVY:
			for (x = 0; x < m_width && dest < dataend; x++)
				*dest++ = *source++;
			break;

		case FORMAT_VYUY:
		case FORMAT_YUY2:
			for (x = 0; x < m_width && dest < dataend; x++)
			{
				std::uint16_t pix = *source++;
				*dest++ = (pix >> 8) | (pix << 8);
			}
			break;
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    huffyuv_extract_tables - extract HuffYUV
    tables
-------------------------------------------------*/

/**
 * @fn  static avi_error huffyuv_extract_tables(avi_stream *stream, const std::uint8_t *chunkdata, std::uint32_t size)
 *
 * @brief   Huffyuv extract tables.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   chunkdata       The chunkdata.
 * @param   size            The size.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size)
{
	const std::uint8_t *const chunkend = chunkdata + size;

	/* allocate memory for the data */
	std::unique_ptr<huffyuv_data> huffyuv;
	try { huffyuv = std::make_unique<huffyuv_data>(); }
	catch (...) { return avi_file::error::NO_MEMORY; }

	/* extract predictor information */
	if (&chunkdata[40] >= chunkend)
		return avi_file::error::INVALID_DATA;
	huffyuv->predictor = chunkdata[40];

	/* make sure it's the left predictor */
	if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT)
		return avi_file::error::UNSUPPORTED_VIDEO_FORMAT;

	/* make sure it's 16bpp YUV data */
	if (chunkdata[41] != 16)
		return avi_file::error::UNSUPPORTED_VIDEO_FORMAT;
	chunkdata += 44;

	/* loop over tables */
	for (int tabnum = 0; tabnum < 3; tabnum++)
	{
		huffyuv_table &table = huffyuv->table[tabnum];
		std::uint32_t curbits, bitadd;
		std::uint16_t bitsat16 = 0;
		int offset = 0, bits;

		/* loop until we populate the whole table */
		while (offset < 256)
		{
			int data, shift, count, i;

			/* extract the next run */
			if (chunkdata >= chunkend)
				return avi_file::error::INVALID_DATA;
			data = *chunkdata++;
			shift = data & 0x1f;
			count = data >> 5;

			/* zero count means next whole byte is a count */
			if (count == 0)
			{
				if (chunkdata >= chunkend)
					return avi_file::error::INVALID_DATA;
				count = *chunkdata++;
			}
			for (i = 0; i < count; i++)
				table.shift[offset++] = shift;
		}

		/* now determine bit patterns and masks */
		curbits = 0;
		for (bits = 31; bits >= 0; bits--)
		{
			bitadd = 1 << (32 - bits);

			/* make sure we've cleared out all the bits below */
			if ((curbits & (bitadd - 1)) != 0)
				return avi_file::error::INVALID_DATA;

			/* find all entries with this shift count and assign them */
			for (offset = 0; offset < 256; offset++)
				if (table.shift[offset] == bits)
				{
					table.bits[offset] = curbits;
					table.mask[offset] = ~(bitadd - 1);
					curbits += bitadd;
				}

			/* remember the bit pattern when we complete all the 17-bit codes */
			if (bits == 17)
				bitsat16 = curbits >> 16;
		}

		/* allocate the number of extra lookup tables we need */
		if (bitsat16 > 0)
		{
			try { table.extralookup.resize(bitsat16 * 65536); }
			catch (...) { return avi_file::error::NO_MEMORY; }
			for (offset = 0; offset < bitsat16; offset++)
				table.baselookup[offset] = (offset << 8) | 0;
		}

		/* then create lookup tables */
		for (offset = 0; offset < 256; offset++)
			if (table.shift[offset] > 16)
			{
				std::uint16_t *tablebase = &table.extralookup[(table.bits[offset] >> 16) * 65536];
				std::uint32_t start = table.bits[offset] & 0xffff;
				std::uint32_t end = start + ((1 << (32 - table.shift[offset])) - 1);
				while (start <= end)
					tablebase[start++] = (offset << 8) | (table.shift[offset] - 16);
			}
			else if (table.shift[offset] > 0)
			{
				std::uint32_t start = table.bits[offset] >> 16;
				std::uint32_t end = start + ((1 << (16 - table.shift[offset])) - 1);
				while (start <= end)
					table.baselookup[start++] = (offset << 8) | table.shift[offset];
			}
	}

	m_huffyuv = std::move(huffyuv);
	return avi_file::error::NONE;
}


/*-------------------------------------------------
    huffyuv_decompress_to_yuy16 - decompress a
    HuffYUV-encoded frame to a YUY16 bitmap
-------------------------------------------------*/

/**
 * @fn  static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
 *
 * @brief   Huffyuv decompress to yuy 16.
 *
 * @param [in,out]  stream  If non-null, the stream.
 * @param   data            The data.
 * @param   numbytes        The numbytes.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_stream::huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
{
	int prevlines = (m_height > 288) ? 2 : 1;
	std::uint8_t lastprevy = 0, lastprevcb = 0, lastprevcr = 0;
	std::uint8_t lasty = 0, lastcb = 0, lastcr = 0;
	std::uint8_t bitsinbuffer = 0;
	std::uint32_t bitbuffer = 0;
	std::uint32_t dataoffs = 0;
	int x, y;

	/* compressed video */
	for (y = 0; y < m_height; y++)
	{
		std::uint16_t *dest = &bitmap.pix16(y);

		/* handle the first four bytes independently */
		x = 0;
		if (y == 0)
		{
			/* first DWORD is stored as YUY2 */
			lasty = data[dataoffs++];
			lastcb = data[dataoffs++];
			*dest++ = (lasty << 8) | lastcb;
			lasty = data[dataoffs++];
			lastcr = data[dataoffs++];
			*dest++ = (lasty << 8) | lastcr;
			x = 2;
		}

		/* loop over pixels */
		for ( ; x < m_width; x++)
		{
			huffyuv_table const &ytable = m_huffyuv->table[0];
			huffyuv_table const &ctable = m_huffyuv->table[1 + (x & 1)];
			std::uint16_t huffdata;
			int shift;

			/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
			while (bitsinbuffer <= 24 && dataoffs < numbytes)
			{
				bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
				bitsinbuffer += 8;
			}

			/* look up the Y component */
			huffdata = ytable.baselookup[bitbuffer >> 16];
			shift = huffdata & 0xff;
			if (shift == 0)
			{
				bitsinbuffer -= 16;
				bitbuffer <<= 16;

				/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
				while (bitsinbuffer <= 24 && dataoffs < numbytes)
				{
					bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
					bitsinbuffer += 8;
				}

				huffdata = ytable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
				shift = huffdata & 0xff;
			}
			bitsinbuffer -= shift;
			bitbuffer <<= shift;
			std::uint16_t const pixel = huffdata & 0xff00;

			/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
			while (bitsinbuffer <= 24 && dataoffs < numbytes)
			{
				bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
				bitsinbuffer += 8;
			}

			/* look up the Cb/Cr component */
			huffdata = ctable.baselookup[bitbuffer >> 16];
			shift = huffdata & 0xff;
			if (shift == 0)
			{
				bitsinbuffer -= 16;
				bitbuffer <<= 16;

				/* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
				while (bitsinbuffer <= 24 && dataoffs < numbytes)
				{
					bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
					bitsinbuffer += 8;
				}

				huffdata = ctable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
				shift = huffdata & 0xff;
			}
			bitsinbuffer -= shift;
			bitbuffer <<= shift;
			*dest++ = pixel | (huffdata >> 8);
		}
	}

	/* apply deltas */
	lastprevy = lastprevcb = lastprevcr = 0;
	for (y = 0; y < m_height; y++)
	{
		std::uint16_t *prevrow = &bitmap.pix16(y - prevlines);
		std::uint16_t *dest = &bitmap.pix16(y);

		/* handle the first four bytes independently */
		x = 0;
		if (y == 0)
		{
			/* lasty, lastcr, lastcb are set up previously */
			x = 2;
		}

		/* left predict or gradient predict */
		if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT ||
			((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT))
		{
			/* first do left deltas */
			for ( ; x < m_width; x += 2)
			{
				std::uint16_t pixel0 = dest[x + 0];
				std::uint16_t pixel1 = dest[x + 1];

				lasty += pixel0 >> 8;
				lastcb += pixel0;
				dest[x + 0] = (lasty << 8) | lastcb;

				lasty += pixel1 >> 8;
				lastcr += pixel1;
				dest[x + 1] = (lasty << 8) | lastcr;
			}

			/* for gradient, we then add in the previous row */
			if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines)
				for (x = 0; x < m_width; x++)
				{
					std::uint16_t curpix = dest[x];
					std::uint16_t prevpix = prevrow[x];
					std::uint8_t ysum = (curpix >> 8) + (prevpix >> 8);
					std::uint8_t csum = curpix + prevpix;
					dest[x] = (ysum << 8) | csum;
				}
		}

		/* median predict on rows > 0 */
		else if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines)
		{
			for ( ; x < m_width; x += 2)
			{
				std::uint16_t prevpixel0 = prevrow[x + 0];
				std::uint16_t prevpixel1 = prevrow[x + 1];
				std::uint16_t pixel0 = dest[x + 0];
				std::uint16_t pixel1 = dest[x + 1];
				std::uint8_t a, b, c;

				/* compute previous, above, and (prev + above - above-left) */
				a = lasty;
				b = prevpixel0 >> 8;
				c = lastprevy;
				lastprevy = b;
				if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
				lasty = (pixel0 >> 8) + b;

				/* compute previous, above, and (prev + above - above-left) */
				a = lastcb;
				b = prevpixel0 & 0xff;
				c = lastprevcb;
				lastprevcb = b;
				if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
				lastcb = (pixel0 & 0xff) + b;
				dest[x + 0] = (lasty << 8) | lastcb;

				/* compute previous, above, and (prev + above - above-left) */
				a = lasty;
				b = prevpixel1 >> 8;
				c = lastprevy;
				lastprevy = b;
				if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
				lasty = (pixel1 >> 8) + b;

				/* compute previous, above, and (prev + above - above-left) */
				a = lastcr;
				b = prevpixel1 & 0xff;
				c = lastprevcr;
				lastprevcr = b;
				if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
				if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
				if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
				lastcr = (pixel1 & 0xff) + b;
				dest[x + 1] = (lasty << 8) | lastcr;
			}
		}
	}

	return avi_file::error::NONE;
}


/*-------------------------------------------------
    avi_close - close an AVI movie file
-------------------------------------------------*/

/**
 * @fn  avi_error avi_close(avi_file *file)
 *
 * @brief   Avi close.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file_impl::~avi_file_impl()
{
	error avierr = error::NONE;

	/* if we're creating a new file, finalize it by writing out the non-media chunks */
	if (m_type == FILETYPE_CREATE)
	{
		/* flush any pending sound data */
		avierr = soundbuf_flush(false);

		/* close the movi chunk */
		if (avierr == error::NONE)
			avierr = chunk_close();

		/* if this is the first RIFF chunk, write an idx1 */
		if (avierr == error::NONE && m_riffbase == 0)
			avierr = write_idx1_chunk();

		/* update the strh and indx chunks for each stream */
		for (int strnum = 0; strnum < m_streams.size(); strnum++)
		{
			if (avierr == error::NONE)
				avierr = write_strh_chunk(m_streams[strnum], false);
			if (avierr == error::NONE)
				avierr = write_indx_chunk(m_streams[strnum], false);
		}

		/* update the avih chunk */
		if (avierr == error::NONE)
			avierr = write_avih_chunk(false);

		/* close the RIFF chunk */
		if (avierr == error::NONE)
			avierr = chunk_close();
	}

	/* close the file */
	m_file.reset();

	//return avierr;
}


/*-------------------------------------------------
    avi_printf_chunks - print the chunks in a file
-------------------------------------------------*/

/**
 * @fn  void avi_printf_chunks(avi_file *file)
 *
 * @brief   Avi printf chunks.
 *
 * @param [in,out]  file    If non-null, the file.
 */

void avi_file_impl::printf_chunks()
{
	printf_chunk_recursive(&m_rootchunk, 0);
}


/*-------------------------------------------------
    get_movie_info - return a pointer to the
    movie info
-------------------------------------------------*/

/**
 * @fn  const avi_movie_info *avi_get_movie_info(avi_file *file)
 *
 * @brief   Avi get movie information.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  null if it fails, else an avi_movie_info*.
 */

avi_file::movie_info const &avi_file_impl::get_movie_info() const
{
	return m_info;
}


/*-------------------------------------------------
    avi_frame_to_sample - convert a frame index
    to a sample index
-------------------------------------------------*/

/**
 * @fn  std::uint32_t avi_first_sample_in_frame(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Avi first sample in frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An std::uint32_t.
 */

std::uint32_t avi_file_impl::first_sample_in_frame(std::uint32_t framenum) const
{
	return framenum_to_samplenum(framenum);
}


/*-------------------------------------------------
    avi_read_video_frame - read video data
    for a particular frame from the AVI file,
    converting to YUY16 format
-------------------------------------------------*/

/**
 * @fn  avi_error avi_read_video_frame(avi_file *file, std::uint32_t framenum, bitmap_yuy16 &bitmap)
 *
 * @brief   Avi read video frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap)
{
	/* get the video stream */
	avi_stream *const stream = get_video_stream();
	if (!stream)
		return error::INVALID_STREAM;

	/* validate our ability to handle the data */
	if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* assume one chunk == one frame */
	if (framenum >= stream->chunks())
		return error::INVALID_FRAME;

	/* we only support YUY-style bitmaps (16bpp) */
	if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
		return error::INVALID_BITMAP;

	/* expand the tempbuffer to hold the data if necessary */
	error avierr = error::NONE;
	avierr = expand_tempbuffer(stream->chunk(framenum).length);
	if (avierr != error::NONE)
		return avierr;

	/* read in the data */
	std::uint32_t bytes_read;
	osd_file::error const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read);
	if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(framenum).length)
		return error::READ_ERROR;

	/* validate this is good data */
	std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]);
	if (chunkid == get_chunkid_for_stream(stream))
	{
		/* HuffYUV-compressed */
		if (stream->format() == FORMAT_HFYU)
			avierr = stream->huffyuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);

		/* other YUV-compressed */
		else
			avierr = stream->yuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
	}
	else
		avierr = error::INVALID_DATA;

	return avierr;
}


/*-------------------------------------------------
    avi_read_sound_samples - read sound sample
    data from an AVI file
-------------------------------------------------*/

/**
 * @fn  avi_error avi_read_sound_samples(avi_file *file, int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
 *
 * @brief   Avi read sound samples.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param   firstsample     The firstsample.
 * @param   numsamples      The numsamples.
 * @param [in,out]  output  If non-null, the output.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
{
	/* get the audio stream */
	int offset = 0;
	avi_stream *const stream = get_audio_stream(channel, offset);
	if (!stream)
		return error::INVALID_STREAM;

	/* validate our ability to handle the data */
	if (stream->format() != 0 || (stream->samplebits() != 8 && stream->samplebits() != 16))
		return error::UNSUPPORTED_AUDIO_FORMAT;

	/* verify we are in range */
	if (firstsample >= stream->samples())
		return error::INVALID_FRAME;
	if (firstsample + numsamples > stream->samples())
		numsamples = stream->samples() - firstsample;

	/* determine bytes per sample */
	std::uint32_t const bytes_per_sample = stream->bytes_per_sample();

	/* loop until all samples have been extracted */
	while (numsamples > 0)
	{
		std::uint32_t chunkbase = 0, chunkend = 0, chunkid;
		std::uint32_t bytes_read, samples_this_chunk;
		int chunknum, sampnum;

		/* locate the chunk with the first sample */
		for (chunknum = 0; chunknum < stream->chunks(); chunknum++)
		{
			chunkend = chunkbase + (stream->chunk(chunknum).length - 8) / bytes_per_sample;
			if (firstsample < chunkend)
				break;
			chunkbase = chunkend;
		}

		/* if we hit the end, fill the rest with silence */
		if (chunknum == stream->chunks())
		{
			std::memset(output, 0, numsamples * 2);
			break;
		}

		/* expand the tempbuffer to hold the data if necessary */
		error avierr = expand_tempbuffer(stream->chunk(chunknum).length);
		if (avierr != error::NONE)
			return avierr;

		/* read in the data */
		auto const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(chunknum).offset, stream->chunk(chunknum).length, bytes_read);
		if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(chunknum).length)
			return error::READ_ERROR;

		/* validate this is good data */
		chunkid = fetch_32bits(&m_tempbuffer[0]);
		if (chunkid != get_chunkid_for_stream(stream))
			return error::INVALID_DATA;

		/* determine how many samples to copy */
		samples_this_chunk = chunkend - firstsample;
		samples_this_chunk = (std::min)(samples_this_chunk, numsamples);

		/* extract 16-bit samples from the chunk */
		if (stream->samplebits() == 16)
		{
			const std::int16_t *base = reinterpret_cast<const std::int16_t *>(&m_tempbuffer[8]);
			base += stream->channels() * (firstsample - chunkbase) + offset;
			for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
			{
				*output++ = LITTLE_ENDIANIZE_INT16(*base);
				base += stream->channels();
			}
		}

		/* extract 8-bit samples from the chunk */
		else if (stream->samplebits() == 8)
		{
			const std::uint8_t *base = &m_tempbuffer[8];
			base += stream->channels() * (firstsample - chunkbase) + offset;
			for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
			{
				*output++ = (*base << 8) - 0x8000;
				base += stream->channels();
			}
		}

		/* update our counters */
		firstsample += samples_this_chunk;
		numsamples -= samples_this_chunk;
	}

	return error::NONE;
}


/*-------------------------------------------------
    avi_append_video_frame_yuy16 - append a frame
    of video in YUY16 format
-------------------------------------------------*/

/**
 * @fn  avi_error avi_append_video_frame(avi_file *file, bitmap_yuy16 &bitmap)
 *
 * @brief   Avi append video frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_video_frame(bitmap_yuy16 &bitmap)
{
	avi_stream *const stream = get_video_stream();
	error avierr;
	std::uint32_t maxlength;

	/* validate our ability to handle the data */
	if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(stream->chunks());
	if (avierr != error::NONE)
		return avierr;

	/* make sure we have enough room */
	maxlength = 2 * stream->width() * stream->height();
	avierr = expand_tempbuffer(maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* now compress the data */
	avierr = stream->yuy16_compress_to_yuy(bitmap, &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
	if (avierr != error::NONE)
		return avierr;

	stream->set_samples(m_info.video_numsamples = stream->chunks());

	return error::NONE;
}


/*-------------------------------------------------
    avi_append_video_frame_rgb32 - append a frame
    of video in RGB32 format
-------------------------------------------------*/

/**
 * @fn  avi_error avi_append_video_frame(avi_file *file, bitmap_rgb32 &bitmap)
 *
 * @brief   Avi append video frame.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  bitmap  The bitmap.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_video_frame(bitmap_rgb32 &bitmap)
{
	avi_stream *const stream = get_video_stream();
	error avierr;
	std::uint32_t maxlength;

	/* validate our ability to handle the data */
	if (stream->format() != 0)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* depth must be 24 */
	if (stream->depth() != 24)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* write out any sound data first */
	avierr = soundbuf_write_chunk(stream->chunks());
	if (avierr != error::NONE)
		return avierr;

	/* make sure we have enough room */
	maxlength = 3 * stream->width() * stream->height();
	avierr = expand_tempbuffer(maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* copy the RGB data to the destination */
	avierr = stream->rgb32_compress_to_rgb(bitmap, &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
	if (avierr != error::NONE)
		return avierr;

	stream->set_samples(m_info.video_numsamples = stream->chunks());

	return error::NONE;
}


/*-------------------------------------------------
    avi_append_sound_samples - append sound
    samples
-------------------------------------------------*/

/**
 * @fn  avi_error avi_append_sound_samples(avi_file *file, int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip)
 *
 * @brief   Avi append sound samples.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   channel         The channel.
 * @param   samples         The samples.
 * @param   numsamples      The numsamples.
 * @param   sampleskip      The sampleskip.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::append_sound_samples(int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip)
{
	std::uint32_t sampoffset = m_soundbuf_chansamples[channel];
	std::uint32_t sampnum;

	/* see if we have enough room in the buffer */
	if (sampoffset + numsamples > m_soundbuf_samples)
		return error::EXCEEDED_SOUND_BUFFER;

	/* append samples to the buffer in little-endian format */
	for (sampnum = 0; sampnum < numsamples; sampnum++)
	{
		std::int16_t data = *samples++;
		samples += sampleskip;
		data = LITTLE_ENDIANIZE_INT16(data);
		m_soundbuf[sampoffset++ * m_info.audio_channels + channel] = data;
	}
	m_soundbuf_chansamples[channel] = sampoffset;

	/* flush any full sound chunks to disk */
	return soundbuf_flush(TRUE);
}


/*-------------------------------------------------
    read_chunk_data - read a chunk's data into
    memory
-------------------------------------------------*/

/**
 * @fn  static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, std::uint8_t **buffer)
 *
 * @brief   Reads chunk data.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   chunk           The chunk.
 * @param [in,out]  buffer  If non-null, the buffer.
 *
 * @return  The chunk data.
 */

avi_file::error avi_file_impl::read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer)
{
	/* allocate memory for the data */
	try { buffer.reset(new std::uint8_t[chunk.size]); }
	catch (...) { return error::NO_MEMORY; }

	/* read from the file */
	std::uint32_t bytes_read;
	osd_file::error const filerr = m_file->read(&buffer[0], chunk.offset + 8, chunk.size, bytes_read);
	if (filerr != osd_file::error::NONE || bytes_read != chunk.size)
	{
		buffer.reset();
		return error::READ_ERROR;
	}

	return error::NONE;
}


/*-------------------------------------------------
    get_first_chunk - get information about the
    first chunk in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
 *
 * @brief   Gets the first chunk.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 *
 * @return  The first chunk.
 */

avi_file::error avi_file_impl::get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk)
{
	std::uint64_t startoffset = (parent != nullptr && parent->type != 0) ? parent->offset + 12 : 0;
	if (parent != nullptr && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0)
		return error::INVALID_DATA;
	return get_next_chunk_internal(parent, newchunk, startoffset);
}


/*-------------------------------------------------
    get_next_chunk - get information about the
    next chunk in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
 *
 * @brief   Gets the next chunk.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 *
 * @return  The next chunk.
 */

avi_file::error avi_file_impl::get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk)
{
	std::uint64_t nextoffset = newchunk.offset + 8 + newchunk.size + (newchunk.size & 1);
	return get_next_chunk_internal(parent, newchunk, nextoffset);
}


/*-------------------------------------------------
    find_first_chunk - get information about the
    first chunk of a particular type in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error find_first_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the first chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found chunk.
 */

avi_file::error avi_file_impl::find_first_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = get_first_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
		if (result.type == findme)
			return error::NONE;

	return avierr;
}


/*-------------------------------------------------
    find_next_chunk - get information about the
    next chunk of a particular type in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error find_next_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the next chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found chunk.
 */

avi_file::error avi_file_impl::find_next_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = get_next_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
		if (result.type == findme)
			return error::NONE;

	return avierr;
}


/*-------------------------------------------------
    find_first_list - get information about the
    first list of a particular type in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error find_first_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the first list.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found list.
 */

avi_file::error avi_file_impl::find_first_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = find_first_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
		if (result.listtype == findme)
			return error::NONE;

	return avierr;
}


/*-------------------------------------------------
    find_next_list - get information about the
    next list of a particular type in a container
-------------------------------------------------*/

/**
 * @fn  static avi_error find_next_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
 *
 * @brief   Searches for the next list.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   findme          The findme.
 * @param   container       The container.
 * @param [out] result      If non-null, the result.
 *
 * @return  The found list.
 */

avi_file::error avi_file_impl::find_next_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
{
	error avierr;

	for (avierr = find_next_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
		if (result.listtype == findme)
			return error::NONE;

	return avierr;
}


/*-------------------------------------------------
    get_next_chunk_internal - fetch the next
    chunk relative to the current one
-------------------------------------------------*/

/**
 * @fn  static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, std::uint64_t offset)
 *
 * @brief   Gets the next chunk internal.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param   parent              The parent.
 * @param [in,out]  newchunk    If non-null, the newchunk.
 * @param   offset              The offset.
 *
 * @return  The next chunk internal.
 */

avi_file::error avi_file_impl::get_next_chunk_internal(const avi_chunk *parent, avi_chunk &newchunk, std::uint64_t offset)
{
	osd_file::error filerr;
	std::uint8_t buffer[12];
	std::uint32_t bytesread;

	/* NULL parent implies the root */
	if (parent == nullptr)
		parent = &m_rootchunk;

	/* start at the current offset */
	newchunk.offset = offset;

	/* if we're past the bounds of the parent, bail */
	if (newchunk.offset + 8 >= parent->offset + 8 + parent->size)
		return error::END;

	/* read the header */
	filerr = m_file->read(buffer, newchunk.offset, 8, bytesread);
	if (filerr != osd_file::error::NONE || bytesread != 8)
		return error::INVALID_DATA;

	/* fill in the new chunk */
	newchunk.type = fetch_32bits(&buffer[0]);
	newchunk.size = fetch_32bits(&buffer[4]);

	/* if we are a list, fetch the list type */
	if (newchunk.type == CHUNKTYPE_LIST || newchunk.type == CHUNKTYPE_RIFF)
	{
		filerr = m_file->read(&buffer[8], newchunk.offset + 8, 4, bytesread);
		if (filerr != osd_file::error::NONE || bytesread != 4)
			return error::INVALID_DATA;
		newchunk.listtype = fetch_32bits(&buffer[8]);
	}

	return error::NONE;
}


/*-------------------------------------------------
    read_movie_data - get data about a movie
-------------------------------------------------*/

/**
 * @fn  static avi_error read_movie_data(avi_file *file)
 *
 * @brief   Reads movie data.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The movie data.
 */

avi_file::error avi_file_impl::read_movie_data()
{
	avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1;
	error avierr;

	/* find the RIFF chunk */
	avierr = find_first_chunk(CHUNKTYPE_RIFF, nullptr, riff);
	if (avierr != error::NONE)
		return avierr;

	/* verify that the RIFF type is AVI */
	if (riff.listtype != LISTTYPE_AVI)
	{
		avierr = error::INVALID_DATA;
		return avierr;
	}

	/* find the hdrl LIST chunk within the RIFF */
	avierr = find_first_list(LISTTYPE_HDRL, &riff, hdrl);
	if (avierr != error::NONE)
		return avierr;

	/* find the avih chunk */
	avierr = find_first_chunk(CHUNKTYPE_AVIH, &hdrl, avih);
	if (avierr != error::NONE)
		return avierr;

	/* parse the avih chunk */
	avierr = parse_avih_chunk(avih);
	if (avierr != error::NONE)
		return avierr;

	/* loop over strl LIST chunks */
	int strindex = 0;
	for (avierr = find_first_list(LISTTYPE_STRL, &hdrl, strl); avierr == error::NONE; avierr = find_next_list(LISTTYPE_STRL, &hdrl, strl))
	{
		/* if we have too many, it's a bad file */
		if (strindex >= m_streams.size())
			return avierr;

		/* find the strh chunk */
		avierr = find_first_chunk(CHUNKTYPE_STRH, &strl, strh);
		if (avierr != error::NONE)
			return avierr;

		/* parse the data */
		avierr = parse_strh_chunk(m_streams[strindex], strh);
		if (avierr != error::NONE)
			return avierr;

		/* find the strf chunk */
		avierr = find_first_chunk(CHUNKTYPE_STRF, &strl, strf);
		if (avierr != error::NONE)
			return avierr;

		/* parse the data */
		avierr = parse_strf_chunk(m_streams[strindex], strf);
		if (avierr != error::NONE)
			return avierr;

		/* find the indx chunk, if present */
		avierr = find_first_chunk(CHUNKTYPE_INDX, &strl, indx);
		if (avierr == error::NONE)
			avierr = parse_indx_chunk(m_streams[strindex], indx);

		/* next stream */
		strindex++;
	}

	/* normalize the error after parsing the stream headers */
	if (avierr == error::END)
		avierr = error::NONE;
	if (avierr != error::NONE)
		return avierr;

	/* find the base of the movi data */
	avierr = find_first_list(LISTTYPE_MOVI, &riff, movi);
	if (avierr != error::NONE)
		return avierr;

	/* find and parse the idx1 chunk within the RIFF (if present) */
	avierr = find_first_chunk(CHUNKTYPE_IDX1, &riff, idx1);
	if (avierr == error::NONE)
		avierr = parse_idx1_chunk(movi.offset + 8, idx1);
	if (avierr != error::NONE)
		return avierr;

	/* now extract the movie info */
	avierr = extract_movie_info();
	return avierr;
}


/*-------------------------------------------------
    extract_movie_info - extract the movie info
    from the streams we've read
-------------------------------------------------*/

/**
 * @fn  static avi_error extract_movie_info(avi_file *file)
 *
 * @brief   Extracts the movie information described by file.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  The extracted movie information.
 */

avi_file::error avi_file_impl::extract_movie_info()
{
	avi_stream *stream;
	int offset;

	/* get the video stream */
	stream = get_video_stream();
	if (stream != nullptr)
	{
		/* fill in the info */
		m_info.video_format = stream->format();
		m_info.video_timescale = stream->rate();
		m_info.video_sampletime = stream->scale();
		m_info.video_numsamples = stream->samples();
		m_info.video_width = stream->width();
		m_info.video_height = stream->height();
	}

	/* get the first audio stream */
	stream = get_audio_stream(0, offset);
	if (stream != nullptr)
	{
		/* fill in the info */
		m_info.audio_format = stream->format();
		m_info.audio_timescale = stream->rate();
		m_info.audio_sampletime = stream->scale();
		m_info.audio_numsamples = stream->samples();
		m_info.audio_channels = 1;
		m_info.audio_samplebits = stream->samplebits();
		m_info.audio_samplerate = stream->samplerate();
	}

	/* now make sure all other audio streams are valid */
	while (nullptr != (stream = get_audio_stream(m_info.audio_channels, offset)))
	{
		/* get the stream info */
		m_info.audio_channels++;

		/* verify compatibility */
		if (m_info.audio_format != stream->format() ||
			m_info.audio_timescale != stream->rate() ||
			m_info.audio_sampletime != stream->scale() ||
			m_info.audio_numsamples != stream->samples() ||
			m_info.audio_samplebits != stream->samplebits() ||
			m_info.audio_samplerate != stream->samplerate())
			return error::INCOMPATIBLE_AUDIO_STREAMS;
	}

	return error::NONE;
}


/*-------------------------------------------------
    parse_avih_chunk - parse an avih header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih)
 *
 * @brief   Parse avih chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  avih    If non-null, the avih.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_avih_chunk(avi_chunk const &avih)
{
	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	error avierr = read_chunk_data(avih, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	std::uint32_t const streams = fetch_32bits(&chunkdata[24]);

	/* allocate memory for the streams */
	m_streams.clear();
	m_streams.resize(streams);

	return avierr;
}


/*-------------------------------------------------
    parse_strh_chunk - parse a strh header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh)
 *
 * @brief   Parse strh chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strh    If non-null, the strh.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_strh_chunk(avi_stream &stream, avi_chunk const &strh)
{
	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	error const avierr = read_chunk_data(strh, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	return stream.set_strh_data(&chunkdata[0], strh.size);
}


/*-------------------------------------------------
    parse_strf_chunk - parse a strf header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
 *
 * @brief   Parse strf chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strf    If non-null, the strf.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_strf_chunk(avi_stream &stream, avi_chunk const &strf)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(strf, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	return stream.set_strf_data(&chunkdata[0], strf.size);
}


/*-------------------------------------------------
    parse_indx_chunk - parse an indx chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
 *
 * @brief   Parse indx chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param [in,out]  strf    If non-null, the strf.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_indx_chunk(avi_stream &stream, avi_chunk const &strf)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(strf, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* extract the data */
	std::uint16_t const longs_per_entry = fetch_16bits(&chunkdata[0]);
	//subtype = chunkdata[2];
	std::uint8_t const type = chunkdata[3];
	std::uint32_t const entries = fetch_32bits(&chunkdata[4]);
	//id = fetch_32bits(&chunkdata[8]);
	std::uint64_t const baseoffset = fetch_64bits(&chunkdata[12]);

	/* if this is a superindex, loop over entries and call ourselves recursively */
	if (type == AVI_INDEX_OF_INDEXES)
	{
		/* validate the size of each entry */
		if (longs_per_entry != 4)
			return error::INVALID_DATA;

		/* loop over entries and create subchunks for each */
		for (std::uint32_t entry = 0; entry < entries; entry++)
		{
			const std::uint8_t *base = &chunkdata[24 + entry * 16];
			osd_file::error filerr;
			avi_chunk subchunk;
			std::uint32_t bytes_read;
			std::uint8_t buffer[8];

			/* go read the subchunk */
			subchunk.offset = fetch_64bits(&base[0]);
			filerr = m_file->read(buffer, subchunk.offset, sizeof(buffer), bytes_read);
			if (filerr != osd_file::error::NONE || bytes_read != sizeof(buffer))
			{
				avierr = error::READ_ERROR;
				break;
			}

			/* parse the data */
			subchunk.type = fetch_32bits(&buffer[0]);
			subchunk.size = fetch_32bits(&buffer[4]);

			/* recursively parse each referenced chunk; stop if we hit an error */
			avierr = parse_indx_chunk(stream, subchunk);
			if (avierr != error::NONE)
				break;
		}
	}

	/* otherwise, this is a standard index */
	else if (type == AVI_INDEX_OF_CHUNKS)
	{
		/* validate the size of each entry */
		if (longs_per_entry != 2 && longs_per_entry != 3)
			return error::INVALID_DATA;

		/* loop over entries and parse out the data */
		for (std::uint32_t entry = 0; entry < entries; entry++)
		{
			const std::uint8_t *base = &chunkdata[24 + entry * 4 * longs_per_entry];
			std::uint32_t offset = fetch_32bits(&base[0]);
			std::uint32_t size = fetch_32bits(&base[4]) & 0x7fffffff;  // bit 31 == NOT a keyframe

			/* set the info for this chunk and advance */
			avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset - 8, size + 8);
			if (avierr != error::NONE)
				break;
		}
	}

	return avierr;
}


/*-------------------------------------------------
    parse_idx1_chunk - parse an idx1 chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error parse_idx1_chunk(avi_file *file, std::uint64_t baseoffset, avi_chunk *idx1)
 *
 * @brief   Parse index 1 chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   baseoffset      The baseoffset.
 * @param [in,out]  idx1    If non-null, the first index.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1)
{
	error avierr;

	/* read the data */
	std::unique_ptr<std::uint8_t []> chunkdata;
	avierr = read_chunk_data(idx1, chunkdata);
	if (avierr != error::NONE)
		return avierr;

	/* loop over entries */
	std::uint32_t const entries = idx1.size / 16;
	for (std::uint32_t entry = 0; entry < entries; entry++)
	{
		std::uint8_t const *const base = &chunkdata[entry * 16];
		std::uint32_t const chunkid = fetch_32bits(&base[0]);
		std::uint32_t const offset = fetch_32bits(&base[8]);
		std::uint32_t const size = fetch_32bits(&base[12]);
		int streamnum;

		/* determine the stream index */
		streamnum = ((chunkid >> 8) & 0xff) - '0';
		streamnum += 10 * ((chunkid & 0xff) - '0');
		if (streamnum >= m_streams.size())
			return error::INVALID_DATA;
		avi_stream &stream = m_streams[streamnum];

		/* set the appropriate entry */
		avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset, size + 8);
		if (avierr != error::NONE)
			return avierr;
	}

	return avierr;
}


/*-------------------------------------------------
    chunk_open - open a new chunk for writing
-------------------------------------------------*/

/**
 * @fn  static avi_error chunk_open(avi_file *file, std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
 *
 * @brief   Queries if a given chunk open.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   listtype        The listtype.
 * @param   estlength       The estlength.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
{
	/* if we're out of stack entries, bail */
	if (m_chunksp >= m_chunkstack.size())
		return error::STACK_TOO_DEEP;
	avi_chunk &chunk = m_chunkstack[m_chunksp++];

	/* set up the chunk information */
	chunk.offset = m_writeoffs;
	chunk.size = estlength;
	chunk.type = type;
	chunk.listtype = listtype;

	/* non-list types */
	if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST)
	{
		std::uint8_t buffer[8];

		/* populate the header */
		put_32bits(&buffer[0], chunk.type);
		put_32bits(&buffer[4], chunk.size);

		/* write the header */
		std::uint32_t written;
		osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
		if (filerr != osd_file::error::NONE || written != sizeof(buffer))
			return error::WRITE_ERROR;
		m_writeoffs += written;
	}

	/* list types */
	else
	{
		std::uint8_t buffer[12];

		/* populate the header */
		put_32bits(&buffer[0], chunk.type);
		put_32bits(&buffer[4], chunk.size);
		put_32bits(&buffer[8], chunk.listtype);

		/* write the header */
		std::uint32_t written;
		osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
		if (filerr != osd_file::error::NONE || written != sizeof(buffer))
			return error::WRITE_ERROR;
		m_writeoffs += written;
	}

	return error::NONE;
}


/*-------------------------------------------------
    chunk_close - finish writing an chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error chunk_close(avi_file *file)
 *
 * @brief   Chunk close.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_close()
{
	avi_chunk const &chunk = m_chunkstack[--m_chunksp];
	std::uint64_t const chunksize = m_writeoffs - (chunk.offset + 8);

	/* error if we don't fit into 32 bits */
	if (chunksize != std::uint32_t(chunksize))
		return error::INVALID_DATA;

	/* write the final size if it is different from the guess */
	if (chunk.size != chunksize)
	{
		std::uint8_t buffer[4];

		put_32bits(&buffer[0], std::uint32_t(chunksize));
		std::uint32_t written;
		osd_file::error const filerr = m_file->write(buffer, chunk.offset + 4, sizeof(buffer), written);
		if (filerr != osd_file::error::NONE || written != sizeof(buffer))
			return error::WRITE_ERROR;
	}

	/* round up to the next word */
	m_writeoffs += chunksize & 1;

	return error::NONE;
}


/*-------------------------------------------------
    chunk_write - write an chunk and its data
-------------------------------------------------*/

/**
 * @fn  static avi_error chunk_write(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length)
 *
 * @brief   Chunk write.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   data            The data.
 * @param   length          The length.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_write(std::uint32_t type, const void *data, std::uint32_t length)
{
	error avierr;

	/* if we are the first RIFF, we must reserve enough space for the IDX chunk */
	std::uint32_t const idxreserve = (m_riffbase == 0 && type != CHUNKTYPE_IDX1) ? compute_idx1_size() : 0;

	/* if we are getting too big, split the RIFF */
	/* note that we ignore writes before the current RIFF base, as those are assumed to be
	   overwrites of a chunk from the previous RIFF */
	if ((m_writeoffs >= m_riffbase) && ((m_writeoffs + length + idxreserve - m_riffbase) >= MAX_RIFF_SIZE))
	{
		/* close the movi list */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;

		/* write the idx1 chunk if this is the first */
		if (m_riffbase == 0)
		{
			avierr = write_idx1_chunk();
			if (avierr != error::NONE)
				return avierr;
		}

		/* close the RIFF */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;

		/* open a new RIFF */
		m_riffbase = m_writeoffs;
		avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0);
		if (avierr != error::NONE)
			return avierr;

		/* open a nested movi list */
		m_saved_movi_offset = m_writeoffs;
		avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
		if (avierr != error::NONE)
			return avierr;
	}

	/* open the chunk */
	avierr = chunk_open(type, 0, length);
	if (avierr != error::NONE)
		return avierr;

	/* write the data */
	std::uint32_t written;
	osd_file::error const filerr = m_file->write(data, m_writeoffs, length, written);
	if (filerr != osd_file::error::NONE || written != length)
		return error::WRITE_ERROR;
	m_writeoffs += written;

	/* close the chunk */
	return chunk_close();
}


/*-------------------------------------------------
    chunk_overwrite - write a chunk in two passes;
    first pass writes to the end of file and
    saves the offset; second pass overwrites the
    original
-------------------------------------------------*/

/**
 * @fn  static avi_error chunk_overwrite(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t *offset, int initial_write)
 *
 * @brief   Chunk overwrite.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   type            The type.
 * @param   data            The data.
 * @param   length          The length.
 * @param [in,out]  offset  If non-null, the offset.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write)
{
	std::uint64_t savedoffset = 0;

	/* if this is our initial write, save the offset */
	if (initial_write)
		offset = m_writeoffs;

	/* otherwise, remember the current write offset and replace it with the original */
	else
	{
		savedoffset = m_writeoffs;
		m_writeoffs = offset;
	}

	/* write the chunk */
	error const avierr = chunk_write(type, data, length);

	/* if this isn't the initial write, restore the previous offset */
	if (!initial_write)
		m_writeoffs = savedoffset;

	return avierr;
}


/*-------------------------------------------------
    write_initial_headers - write out the inital
    set of AVI and stream headers
-------------------------------------------------*/

/**
 * @fn  static avi_error write_initial_headers(avi_file *file)
 *
 * @brief   Writes an initial headers.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_initial_headers()
{
	error avierr;

	/* reset the write pointer */
	m_writeoffs = 0;

	/* open a RIFF chunk */
	avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVI, 0);
	if (avierr != error::NONE)
		return avierr;

	/* open a hdlr LIST */
	avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_HDRL, 0);
	if (avierr != error::NONE)
		return avierr;

	/* write an avih chunk */
	avierr = write_avih_chunk(true);
	if (avierr != error::NONE)
		return avierr;

	/* for each stream, write a strl LIST */
	for (int strnum = 0; strnum < m_streams.size(); strnum++)
	{
		/* open a strl LIST */
		avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_STRL, 0);
		if (avierr != error::NONE)
			return avierr;

		/* write the strh chunk */
		avierr = write_strh_chunk(m_streams[strnum], true);
		if (avierr != error::NONE)
			return avierr;

		/* write the strf chunk */
		avierr = write_strf_chunk(m_streams[strnum]);
		if (avierr != error::NONE)
			return avierr;

		/* write the indx chunk */
		avierr = write_indx_chunk(m_streams[strnum], true);
		if (avierr != error::NONE)
			return avierr;

		/* close the strl LIST */
		avierr = chunk_close();
		if (avierr != error::NONE)
			return avierr;
	}

	/* close the hdlr LIST */
	avierr = chunk_close();
	if (avierr != error::NONE)
		return avierr;

	/* open a movi LIST */
	m_saved_movi_offset = m_writeoffs;
	avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
	if (avierr != error::NONE)
		return avierr;

	return avierr;
}


/*-------------------------------------------------
    write_avih_chunk - write the avih header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error write_avih_chunk(avi_file *file, int initial_write)
 *
 * @brief   Writes an avih chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_avih_chunk(bool initial_write)
{
	avi_stream *video = get_video_stream();
	std::uint8_t buffer[56];

	/* reset the buffer */
	std::memset(buffer, 0, sizeof(buffer));

	put_32bits(&buffer[0], 1000000 * std::int64_t(video->scale()) / video->rate()); /* dwMicroSecPerFrame */
	put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */
	put_32bits(&buffer[16], video->samples());          /* dwTotalFrames */
	put_32bits(&buffer[24], m_streams.size());          /* dwStreams */
	put_32bits(&buffer[32], video->width());            /* dwWidth */
	put_32bits(&buffer[36], video->height());           /* dwHeight */

	/* (over)write the chunk */
	return chunk_overwrite(CHUNKTYPE_AVIH, buffer, sizeof(buffer), m_saved_avih_offset, initial_write);
}


/*-------------------------------------------------
    write_strh_chunk - write the strh header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write)
 *
 * @brief   Writes a strh chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_strh_chunk(avi_stream &stream, bool initial_write)
{
	std::uint8_t buffer[56];

	/* reset the buffer */
	std::memset(buffer, 0, sizeof(buffer));

	put_32bits(&buffer[0], stream.type());              /* fccType */
	put_32bits(&buffer[20], stream.scale());            /* dwScale */
	put_32bits(&buffer[24], stream.rate());             /* dwRate */
	put_32bits(&buffer[32], stream.samples());          /* dwLength */
	put_32bits(&buffer[40], 10000);                     /* dwQuality */

	/* video-stream specific data */
	if (stream.type() == STREAMTYPE_VIDS)
	{
		put_32bits(&buffer[4],                          /* fccHandler */
					(stream.format() == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB);
		put_32bits(&buffer[36],                         /* dwSuggestedBufferSize */
					stream.width() * stream.height() * 4);
		put_16bits(&buffer[52], stream.width());        /* rcFrame.right */
		put_16bits(&buffer[54], stream.height());       /* rcFrame.bottom */
	}

	/* audio-stream specific data */
	if (stream.type() == STREAMTYPE_AUDS)
	{
		put_32bits(&buffer[36],                         /* dwSuggestedBufferSize */
					stream.samplerate() * stream.bytes_per_sample());
		put_32bits(&buffer[44],                         /* dwSampleSize */
					stream.bytes_per_sample());
	}

	/* write the chunk */
	return chunk_overwrite(CHUNKTYPE_STRH, buffer, sizeof(buffer), stream.saved_strh_offset(), initial_write);
}


/*-------------------------------------------------
    write_strf_chunk - write the strf header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error write_strf_chunk(avi_file *file, avi_stream *stream)
 *
 * @brief   Writes a strf chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_strf_chunk(avi_stream const &stream)
{
	/* video stream */
	if (stream.type() == STREAMTYPE_VIDS)
	{
		std::uint8_t buffer[40];

		/* reset the buffer */
		std::memset(buffer, 0, sizeof(buffer));

		put_32bits(&buffer[0], sizeof(buffer));         /* biSize */
		put_32bits(&buffer[4], stream.width());         /* biWidth */
		put_32bits(&buffer[8], stream.height());        /* biHeight */
		put_16bits(&buffer[12], 1);                     /* biPlanes */
		put_16bits(&buffer[14], stream.depth());        /* biBitCount */
		put_32bits(&buffer[16], stream.format());       /* biCompression */
		put_32bits(&buffer[20],                         /* biSizeImage */
					stream.width() * stream.height() * (stream.depth() + 7) / 8);

		/* write the chunk */
		return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer));
	}

	/* audio stream */
	if (stream.type() == STREAMTYPE_AUDS)
	{
		std::uint8_t buffer[16];

		/* reset the buffer */
		std::memset(buffer, 0, sizeof(buffer));

		put_16bits(&buffer[0], 1);                      /* wFormatTag */
		put_16bits(&buffer[2], stream.channels());      /* nChannels */
		put_32bits(&buffer[4], stream.samplerate());    /* nSamplesPerSec */
		put_32bits(&buffer[8],                          /* nAvgBytesPerSec */
					stream.samplerate() * stream.bytes_per_sample());
		put_16bits(&buffer[12],                         /* nBlockAlign */
					stream.bytes_per_sample());
		put_16bits(&buffer[14], stream.samplebits());   /* wBitsPerSample */

		/* write the chunk */
		return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer));
	}

	return error::INVALID_DATA;
}


/*-------------------------------------------------
    write_indx_chunk - write the indx header
    chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write)
 *
 * @brief   Writes an indx chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param [in,out]  stream  If non-null, the stream.
 * @param   initial_write   The initial write.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_indx_chunk(avi_stream &stream, bool initial_write)
{
	std::uint8_t buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4];
	std::uint32_t master_entries = 0;

	/* reset the buffer */
	std::memset(buffer, 0, sizeof(buffer));

	/* construct the chunk ID and index chunk ID */
	std::uint32_t const chunkid = get_chunkid_for_stream(&stream);
	std::uint32_t const indexchunkid = AVI_FOURCC('i', 'x', '0' + (&stream - &m_streams[0]) / 10, '0' + (&stream - &m_streams[0]) % 10);

	/* loop over chunks of 4GB and write out indexes for them first */
	if (!initial_write && m_riffbase != 0)
	{
		for (std::uint64_t currentbase = 0; currentbase < m_writeoffs; currentbase += FOUR_GB)
		{
			std::uint64_t const currentend = currentbase + FOUR_GB;
			std::uint32_t chunks_this_index = 0;
			std::uint32_t bytes_this_index = 0;

			/* count chunks that are in this region */
			for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++)
				if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend)
					chunks_this_index++;

			/* if no chunks, skip */
			if (chunks_this_index == 0)
				continue;

			/* allocate memory */
			std::unique_ptr<std::uint8_t []> tempbuf;
			try { tempbuf.reset(new std::uint8_t[24 + 8 * chunks_this_index]); }
			catch (...) { return error::NO_MEMORY; }
			std::memset(&tempbuf[0], 0, 24 + 8 * chunks_this_index);

			/* make a regular index */
			put_16bits(&tempbuf[0], 2);                     /* wLongsPerEntry */
			tempbuf[2] = 0;                                 /* bIndexSubType */
			tempbuf[3] = AVI_INDEX_OF_CHUNKS;               /* bIndexType */
			put_32bits(&tempbuf[4], chunks_this_index);     /* nEntriesInUse */
			put_32bits(&tempbuf[8], chunkid);               /* dwChunkId */
			put_64bits(&tempbuf[12], currentbase);          /* qwBaseOffset */

			/* now fill in the indexes */
			chunks_this_index = 0;
			for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++)
				if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend)
				{
					put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream.chunk(chunknum).offset + 8 - currentbase);
					put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream.chunk(chunknum).length - 8);
					bytes_this_index += stream.chunk(chunknum).length;
					chunks_this_index++;
				}

			/* write the offset of this index to the master table */
			put_64bits(&buffer[24 + 16 * master_entries + 0], m_writeoffs);
			put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8);
			if (stream.type() == STREAMTYPE_VIDS)
				put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index);
			else if (stream.type() == STREAMTYPE_AUDS)
				put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / stream.bytes_per_sample());
			master_entries++;

			/* write the index */
			error const avierr = chunk_write(indexchunkid, &tempbuf[0], 24 + 8 * chunks_this_index);
			if (avierr != error::NONE)
				return avierr;
		}
	}

	/* build up the master index */
	if (master_entries != 0)
	{
		put_16bits(&buffer[0], 4);                      /* wLongsPerEntry */
		buffer[2] = 0;                                  /* bIndexSubType */
		buffer[3] = AVI_INDEX_OF_INDEXES;               /* bIndexType */
		put_32bits(&buffer[4], master_entries);         /* nEntriesInUse */
		put_32bits(&buffer[8], chunkid);                /* dwChunkId */
	}

	/* (over)write the chunk */
	return chunk_overwrite((master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), stream.saved_indx_offset(), initial_write);
}


/*-------------------------------------------------
    write_idx1_chunk - write the idx1 chunk
-------------------------------------------------*/

/**
 * @fn  static avi_error write_idx1_chunk(avi_file *file)
 *
 * @brief   Writes an index 1 chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::write_idx1_chunk()
{
	std::uint32_t const tempbuflength = compute_idx1_size() - 8;
	std::uint32_t curchunk[2] = { 0 };

	/* allocate a temporary buffer */
	std::unique_ptr<std::uint8_t []> tempbuf;
	try { tempbuf.reset(new std::uint8_t[tempbuflength]); }
	catch (...) { return error::NO_MEMORY; }

	/* fill it in */
	for (std::uint32_t curoffset = 0; curoffset < tempbuflength; curoffset += 16)
	{
		std::uint64_t minoffset = ~std::uint64_t(0);
		int minstr = 0;

		/* determine which stream has the next chunk */
		for (int strnum = 0; strnum < m_streams.size(); strnum++)
			if (curchunk[strnum] < m_streams[strnum].chunks() && m_streams[strnum].chunk(curchunk[strnum]).offset < minoffset)
			{
				minoffset = m_streams[strnum].chunk(curchunk[strnum]).offset;
				minstr = strnum;
			}

		/* make an entry for this index */
		put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(&m_streams[minstr]));
		put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */);
		put_32bits(&tempbuf[curoffset + 8], minoffset - (m_saved_movi_offset + 8));
		put_32bits(&tempbuf[curoffset + 12], m_streams[minstr].chunk(curchunk[minstr]).length - 8);

		/* advance the chunk counter for this stream */
		curchunk[minstr]++;
	}

	/* write the chunk */
	return chunk_write(CHUNKTYPE_IDX1, &tempbuf[0], tempbuflength);
}


/*-------------------------------------------------
    soundbuf_initialize - initialize the sound
    buffering system
-------------------------------------------------*/

/**
 * @fn  static avi_error soundbuf_initialize(avi_file *file)
 *
 * @brief   Soundbuf initialize.
 *
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_initialize()
{
	int offset;
	avi_stream *const audio = get_audio_stream(0, offset);
	avi_stream *const video = get_video_stream();

	/* we require a video stream */
	if (video == nullptr)
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* skip if no audio stream */
	if (audio == nullptr)
		return error::NONE;

	/* determine the number of samples we want in our buffer; 2 seconds should be enough */
	m_soundbuf_samples = m_info.audio_samplerate * SOUND_BUFFER_MSEC / 1000;

	/* allocate a buffer */
	m_soundbuf.clear();
	try { m_soundbuf.resize(m_soundbuf_samples * m_info.audio_channels, 0); }
	catch (...) { return error::NO_MEMORY; }

	/* determine the number of frames to be ahead (0.75secs) */
	m_soundbuf_frames = (std::uint64_t(video->rate()) * 75) / (std::uint64_t(video->scale()) * 100) + 1;
	return error::NONE;
}


/*-------------------------------------------------
    soundbuf_write_chunk - write the initial
    chunk data
-------------------------------------------------*/

/**
 * @fn  static avi_error soundbuf_write_chunk(avi_file *file, std::uint32_t framenum)
 *
 * @brief   Soundbuf write chunk.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   framenum        The framenum.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_write_chunk(std::uint32_t framenum)
{
	int offset;
	avi_stream *const stream = get_audio_stream(0, offset);
	std::uint32_t length;

	/* skip if no audio stream */
	if (stream == nullptr)
		return error::NONE;

	/* determine the length of this chunk */
	if (framenum == 0)
		length = framenum_to_samplenum(m_soundbuf_frames);
	else
		length = framenum_to_samplenum(framenum + 1 + m_soundbuf_frames) - framenum_to_samplenum(framenum + m_soundbuf_frames);
	length *= stream->channels() * sizeof(std::int16_t);

	/* then do the initial write */
	error const avierr = chunk_write(get_chunkid_for_stream(stream), &m_soundbuf[0], length);
	if (avierr != error::NONE)
		return avierr;

	/* set the info for this new chunk */
	return stream->set_chunk_info(stream->chunks(), m_writeoffs - length - 8, length + 8);
}


/*-------------------------------------------------
    soundbuf_flush - flush data from the sound
    buffers
-------------------------------------------------*/

/**
 * @fn  static avi_error soundbuf_flush(avi_file *file, int only_flush_full)
 *
 * @brief   Soundbuf flush.
 *
 * @param [in,out]  file    If non-null, the file.
 * @param   only_flush_full The only flush full.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file_impl::soundbuf_flush(bool only_flush_full)
{
	int offset;
	avi_stream *const stream = get_audio_stream(0, offset);
	std::int32_t channelsamples = m_soundbuf_samples;
	std::int32_t processedsamples = 0;
	std::uint32_t finalchunks;
	error avierr;
	std::uint32_t chunknum;
	std::uint32_t chunkid;

	/* skip if no stream */
	if (stream == nullptr)
		return error::NONE;

	/* get the chunk ID for this stream */
	chunkid = get_chunkid_for_stream(stream);
	std::uint32_t const bytes_per_sample = stream->channels() * sizeof(std::int16_t);
	finalchunks = stream->chunks();

	/* find out how many samples we've accumulated */
	for (int channel = 0; channel < stream->channels(); channel++)
		channelsamples = (std::min<std::int32_t>)(channelsamples, m_soundbuf_chansamples[channel]);

	/* loop over pending sound chunks */
	for (chunknum = m_soundbuf_chunks; chunknum < stream->chunks(); chunknum++)
	{
		avi_chunk_list &chunk = stream->chunk(chunknum);
		std::uint32_t const chunksamples = (chunk.length - 8) / bytes_per_sample;

		/* stop if we don't have enough to satisfy this chunk */
		if (only_flush_full && channelsamples < chunksamples)
			break;

		/* if we don't have all the samples we need, pad with 0's */
		if (channelsamples > 0 && channelsamples < chunksamples)
		{
			if (processedsamples + chunksamples > m_soundbuf_samples)
				return error::EXCEEDED_SOUND_BUFFER;
			std::memset(&m_soundbuf[(processedsamples + channelsamples) * stream->channels()], 0, (chunksamples - channelsamples) * bytes_per_sample);
		}

		/* if we're completely out of samples, clear the buffer entirely and use the end */
		else if (channelsamples <= 0)
		{
			processedsamples = m_soundbuf_samples - chunksamples;
			std::memset(&m_soundbuf[processedsamples * stream->channels()], 0, chunksamples * bytes_per_sample);
			chunkid = CHUNKTYPE_JUNK;
			finalchunks--;
		}

		/* copy the sample data in */
		avierr = chunk_overwrite(chunkid, &m_soundbuf[processedsamples * stream->channels()], chunk.length - 8, chunk.offset, false);
		if (avierr != error::NONE)
			return avierr;

		/* add up the samples */
		if (channelsamples > chunksamples)
			m_info.audio_numsamples = stream->add_samples(chunksamples);
		else if (channelsamples > 0)
			m_info.audio_numsamples = stream->add_samples(channelsamples);

		/* advance past those */
		processedsamples += chunksamples;
		channelsamples -= chunksamples;
		channelsamples = (std::max)(0, channelsamples);
	}

	/* if we have a non-zero offset, shift everything down */
	if (processedsamples > 0)
	{
		/* first account for the samples we processed */
		std::memmove(&m_soundbuf[0], &m_soundbuf[processedsamples * stream->channels()], (m_soundbuf_samples - processedsamples) * bytes_per_sample);
		for (int channel = 0; channel < stream->channels(); channel++)
			m_soundbuf_chansamples[channel] -= processedsamples;
	}

	/* update the final chunk count */
	if (!only_flush_full)
		stream->set_chunks(finalchunks);

	/* account for flushed chunks */
	m_soundbuf_chunks = chunknum;
	return error::NONE;
}


/*-------------------------------------------------
    printf_chunk_recursive - print information
    about a chunk recursively
-------------------------------------------------*/

/**
 * @fn  static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent)
 *
 * @brief   Printf chunk recursive.
 *
 * @param [in,out]  file        If non-null, the file.
 * @param [in,out]  container   If non-null, the container.
 * @param   indent              The indent.
 */

void avi_file_impl::printf_chunk_recursive(avi_chunk const *container, int indent)
{
	char size[20], offset[20];
	avi_chunk curchunk;
	error avierr;

	/* iterate over chunks in this container */
	for (avierr = get_first_chunk(container, curchunk); avierr == error::NONE; avierr = get_next_chunk(container, curchunk))
	{
		std::uint32_t chunksize = curchunk.size;
		bool recurse = false;

		u64toa(curchunk.size, size);
		u64toa(curchunk.offset, offset);
		printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "",
				std::uint8_t(curchunk.type >> 0),
				std::uint8_t(curchunk.type >> 8),
				std::uint8_t(curchunk.type >> 16),
				std::uint8_t(curchunk.type >> 24),
				size, offset);

		/* certain chunks are just containers; recurse into them */
		switch (curchunk.type)
		{
			/* basic containers */
			case CHUNKTYPE_RIFF:
			case CHUNKTYPE_LIST:
				printf("%*stype = %c%c%c%c\n", indent, "",
						std::uint8_t(curchunk.listtype >> 0),
						std::uint8_t(curchunk.listtype >> 8),
						std::uint8_t(curchunk.listtype >> 16),
						std::uint8_t(curchunk.listtype >> 24));
				recurse = true;
				chunksize = 0;
				break;
		}

		/* print data within the chunk */
		if (chunksize > 0 && curchunk.size < 1024 * 1024)
		{
			std::unique_ptr<std::uint8_t []> data;

			/* read the data for a chunk */
			avierr = read_chunk_data(curchunk, data);
			if (avierr == error::NONE)
			{
				std::uint32_t const bytes = (std::min)(std::uint32_t(512), chunksize);
				for (std::uint32_t i = 0; i < bytes; i++)
				{
					if (i % 16 == 0) printf("%*s   ", indent, "");
					printf("%02X ", data[i]);
					if (i % 16 == 15) printf("\n");
				}
				if (chunksize % 16 != 0) printf("\n");
			}
		}

		/* if we're recursing, dive down */
		if (recurse)
			printf_chunk_recursive(&curchunk, indent + 4);
	}

	/* if we didn't get a legitimate error, indicate that */
	if (avierr != error::END)
		printf("[chunk error %d]\n", int(avierr));
}

} // anonymous namespace



/***************************************************************************
    IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    avi_open - open an AVI movie file for read
-------------------------------------------------*/

/**
 * @fn  avi_error avi_open(const char *filename, avi_file **file)
 *
 * @brief   Queries if a given avi open.
 *
 * @param   filename        Filename of the file.
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file::open(std::string const &filename, ptr &file)
{
	/* open the file */
	osd_file::ptr f;
	std::uint64_t length;
	if (osd_file::open(filename, OPEN_FLAG_READ, f, length) != osd_file::error::NONE)
		return error::CANT_OPEN_FILE;

	/* allocate the file */
	std::unique_ptr<avi_file_impl> newfile;
	try { newfile = std::make_unique<avi_file_impl>(std::move(f), length); }
	catch (...) { return error::NO_MEMORY; }

	/* parse the data */
	error const avierr = newfile->read_movie_data();
	if (avierr != error::NONE)
		return avierr;

	file = std::move(newfile);
	return error::NONE;
}


/*-------------------------------------------------
    avi_create - create a new AVI movie file
-------------------------------------------------*/

/**
 * @fn  avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file)
 *
 * @brief   Avi create.
 *
 * @param   filename        Filename of the file.
 * @param   info            The information.
 * @param [in,out]  file    If non-null, the file.
 *
 * @return  An avi_error.
 */

avi_file::error avi_file::create(std::string const &filename, movie_info const &info, ptr &file)
{
	/* validate video info */
	if ((info.video_format != 0 && info.video_format != FORMAT_UYVY && info.video_format != FORMAT_VYUY && info.video_format != FORMAT_YUY2) ||
		(info.video_width == 0) ||
		(info.video_height == 0) ||
		(info.video_depth == 0) ||
		(info.video_depth % 8 != 0))
		return error::UNSUPPORTED_VIDEO_FORMAT;

	/* validate audio info */
	if (info.audio_format != 0 ||
		info.audio_channels > MAX_SOUND_CHANNELS ||
		info.audio_samplebits != 16)
		return error::UNSUPPORTED_AUDIO_FORMAT;

	/* open the file */
	osd_file::ptr f;
	std::uint64_t length;
	osd_file::error const filerr = osd_file::open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, f, length);
	if (filerr != osd_file::error::NONE)
		return error::CANT_OPEN_FILE;

	/* allocate the file */
	error avierr;
	std::unique_ptr<avi_file_impl> newfile;
	try { newfile = std::make_unique<avi_file_impl>(std::move(f), info); }
	catch (...)
	{
		avierr = error::NO_MEMORY;
		goto error;
	}

	/* initialize the sound buffering */
	avierr = newfile->soundbuf_initialize();
	if (avierr != error::NONE)
		goto error;

	/* write the initial headers */
	avierr = newfile->write_initial_headers();
	if (avierr != error::NONE)
		goto error;

	file = std::move(newfile);
	return error::NONE;

error:
	f.reset();
	newfile.reset();
	osd_file::remove(filename);
	return avierr;
}


/*-------------------------------------------------
    avi_error_string - get the error string for
    an avi_error
-------------------------------------------------*/

/**
 * @fn  const char *avi_error_string(avi_error err)
 *
 * @brief   Avi error string.
 *
 * @param   err The error.
 *
 * @return  null if it fails, else a char*.
 */

const char *avi_file::error_string(error err)
{
	switch (err)
	{
		case error::NONE:                       return "success";
		case error::END:                        return "hit end of file";
		case error::INVALID_DATA:               return "invalid data";
		case error::NO_MEMORY:                  return "out of memory";
		case error::READ_ERROR:                 return "read error";
		case error::WRITE_ERROR:                return "write error";
		case error::STACK_TOO_DEEP:             return "stack overflow";
		case error::UNSUPPORTED_FEATURE:        return "unsupported feature";
		case error::CANT_OPEN_FILE:             return "unable to open file";
		case error::INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams";
		case error::INVALID_SAMPLERATE:         return "found invalid sample rate";
		case error::INVALID_STREAM:             return "invalid stream";
		case error::INVALID_FRAME:              return "invalid frame index";
		case error::INVALID_BITMAP:             return "invalid bitmap";
		case error::UNSUPPORTED_VIDEO_FORMAT:   return "unsupported video format";
		case error::UNSUPPORTED_AUDIO_FORMAT:   return "unsupported audio format";
		case error::EXCEEDED_SOUND_BUFFER:      return "sound buffer overflow";
		default:                                return "undocumented error";
	}
}


avi_file::avi_file()
{
}


avi_file::~avi_file()
{
}